likes
comments
collection
share

JS制作Antv图表实例

作者站长头像
站长
· 阅读数 4

不折腾的前端不是好前端,今天来折腾下Antv图表,对比highcharts这个文档确实不怎么人性化,但是没办法么,毕竟highCharts还是蛮贵的,使用antv之前建议大家看下语雀上的文档。

G2

上手案例

先引入antv的js文件

<script src="https://gw.alipayobjects.com/os/lib/antv/g2/3.4.10/dist/g2.min.js"></script>

在body中创建放图表的容器

<div id="c1"></div>

在页面或者另一个单独的js文件中写图表的渲染代码,我这边是单独写的一个js文件,然后页面通过<script src="index.js"></script>引入

var data = [
    {genre: 'Sports', sold: 275},
    {genre: 'Strategy', sold: 115},
    {genre: 'Action', sold: 120},
    {genre: 'Shooter', sold: 350},
    {genre: 'Other', sold: 150},
  ];
  var chart = new G2.Chart({
    container: 'c1',
    forceFit: true,//图表跟随图表容器宽度变化
    height : 400
  });
  chart.source(data);
  chart.interval().position('genre*sold').color('genre')
  chart.render();

如此,新鲜的图表出炉咯

JS制作Antv图表实例

饼图

先引入需要的js文件

 <script src="https://gw.alipayobjects.com/os/lib/antv/g2/3.4.10/dist/g2.min.js"></script>
<script src="https://unpkg.com/@antv/data-set"></script>

css

 <style>
.g2-guide-html {
        width: 100px;
        height: 80px;
        vertical-align: middle;
        text-align: center;
        line-height: 0.4
    }

    .g2-guide-html .title {
        font-size: 12px;
        color: #8c8c8c;
        font-weight: 300;
    }

    .g2-guide-html .value {
        font-size: 30px;
        color: #000;
        font-weight: bold;
    }


    </style>

html,因为forceFit设置的true随着父级的变化而变化,所以我在外面套了个有长宽的div,当然也可以forceFit设置为false,给容器设置宽高,都可以

  <div style="width: 1000px;height:1000px;">
        <div id="c1" ></div>
    </div>

js文件

const startAngle = -Math.PI / 2 - Math.PI / 4;
const data = [
  { type: '居住', value: 7140 },
  { type: '食品烟酒', value: 3875 },
  { type: '交通通信', value: 2267 },
  { type: '教育、文化、娱乐', value: 1853 },
  { type: '医疗保健', value: 1685 }
];
const ds = new DataSet();
const dv = ds.createView().source(data);//通过打印dv发现dv.rows中除了data的数据,每项添加了percent
dv.transform({
  type: 'percent',
  field: 'value',
  dimension: 'type',
  as: 'percent'
});
const chart = new G2.Chart({
  container: 'c1',
  forceFit: true,
  height: 500,
  padding: 'auto'
});
//载入dv数据
chart.source(dv);
//不显示图例
chart.legend(false);
chart.coord('theta', {
  radius: 0.75,
  innerRadius: 0.5,
  startAngle,
  endAngle: startAngle + Math.PI * 2
});
chart.intervalStack().position('value')
  .color('type', [ '#0a4291', '#0a57b6', '#1373db', '#2295ff', '#48adff' ])//有多少数据写多少颜色
  .opacity(1)//设置透明度
  .label('percent', {
    offset: -20,//图表上显示的数据的偏移位置
    //图表上数据的格式
    textStyle: {
      fill: 'white',
      fontSize: 12,
      shadowBlur: 20,
      shadowColor: 'rgba(0, 0, 0)'
    },
    //图表上显示的数据显示的格式
    formatter: val => {
      return parseInt(val * 100) + '%';
    }
  });
  //Guide 辅助元素:额外的标记注解
chart.guide().html({
  position: [ '50%', '50%' ],
  html: '<div class="g2-guide-html"><p class="title">总计</p><p class="value">19670</p></div>'
});
//渲染图表
chart.render();
// draw label
const OFFSET = 20;
const APPEND_OFFSET = 50;//每个扇形标签相当于x轴的标签的位置
const LINEHEIGHT = 60;
const coord = chart.get('coord'); // 获取坐标系对象
const center = coord.center; // 极坐标圆心坐标
const r = coord.radius; // 极坐标半径
const canvas = chart.get('canvas');
const canvasWidth = chart.get('width');
const canvasHeight = chart.get('height');
const labelGroup = canvas.addGroup();
const labels = [];
addPieLabel(chart);
canvas.draw();
chart.on('afterpaint', function() {
  addPieLabel(chart);
});
// main
//如下函数是用来放置每个扇形区域的标签的
function addPieLabel() {
  const halves = [[], []];
  const data = dv.rows;
  let angle = startAngle;
  for (let i = 0; i < data.length; i++) {
    const percent = data[i].percent;
    const targetAngle = angle + (Math.PI * 2 * percent);//通过计算得出连线放置的位置
    const middleAngle = angle + (targetAngle - angle) / 2;
    angle = targetAngle;
    const edgePoint = getEndPoint(center, middleAngle, r);
    const routerPoint = getEndPoint(center, middleAngle, r + OFFSET);
    // label
    const label = {
      _anchor: edgePoint,
      _router: routerPoint,
      _data: data[i],
      x: routerPoint.x,
      y: routerPoint.y,
      r: r + OFFSET,
      fill: '#bfbfbf'//线的颜色
    };
    // 判断文本的方向
    if (edgePoint.x < center.x) {
      label._side = 'left';
      halves[0].push(label);
    } else {
      label._side = 'right';
      halves[1].push(label);
    }
  }// end of for

  const maxCountForOneSide = parseInt(canvasHeight / LINEHEIGHT, 10);
  halves.forEach(function(half, index) {
    // step 2: reduce labels
    if (half.length > maxCountForOneSide) {
      half.sort(function(a, b) {
        return b._percent - a._percent;
      });
      half.splice(maxCountForOneSide, half.length - maxCountForOneSide);
    }

    // step 3: distribute position (x and y)
    half.sort(function(a, b) {
      return a.y - b.y;
    });
    antiCollision(half, index);
  });
}

function getEndPoint(center, angle, r) {
  return {
    x: center.x + r * Math.cos(angle),
    y: center.y + r * Math.sin(angle)
  };
}

function drawLabel(label) {
  const _anchor = label._anchor,
    _router = label._router,
    fill = label.fill,
    y = label.y;

  const labelAttrs = {
    y,
    fontSize: 12, // 字体大小
    fill: '#808080',
    text: label._data.type + '\n' + label._data.value,
    textBaseline: 'bottom'
  };
  const lastPoint = {
    y
  };

  if (label._side === 'left') {
    // 具体文本的位置
    lastPoint.x = APPEND_OFFSET;
    labelAttrs.x = APPEND_OFFSET; // 左侧文本左对齐并贴着画布最左侧边缘
    labelAttrs.textAlign = 'left';
  } else {
    lastPoint.x = canvasWidth - APPEND_OFFSET;
    labelAttrs.x = canvasWidth - APPEND_OFFSET; // 右侧文本右对齐并贴着画布最右侧边缘
    labelAttrs.textAlign = 'right';
  }

  // 绘制文本
  const text = labelGroup.addShape('Text', {
    attrs: labelAttrs
  });
  labels.push(text);
  // 绘制连接线
  let points = void 0;
  if (_router.y !== y) {
    // 文本位置做过调整
    points = [[ _anchor.x, _anchor.y ], [
      _router.x, y
    ], [ lastPoint.x, lastPoint.y ]];
  } else {
    points = [[ _anchor.x, _anchor.y ], [ _router.x, _router.y ], [ lastPoint.x, lastPoint.y ]];
  }

  labelGroup.addShape('polyline', {
    attrs: {
      points,
      lineWidth: 1,
      stroke: fill
    }
  });
}

function antiCollision(half, isRight) {
  const startY = center.y - r - OFFSET - LINEHEIGHT;
  let overlapping = true;
  let totalH = canvasHeight;
  let i = void 0;

  let maxY = 0;
  let minY = Number.MIN_VALUE;
  const boxes = half.map(function(label) {
    const labelY = label.y;
    if (labelY > maxY) {
      maxY = labelY;
    }
    if (labelY < minY) {
      minY = labelY;
    }
    return {
      size: LINEHEIGHT,
      targets: [ labelY - startY ]
    };
  });
  if (maxY - startY > totalH) {
    totalH = maxY - startY;
  }

  while (overlapping) {
    // eslint-disable-next-line no-loop-func
    boxes.forEach(box => {
      const target = (Math.min.apply(minY, box.targets) + Math.max.apply(minY, box.targets)) / 2;
      box.pos = Math.min(Math.max(minY, target - box.size / 2), totalH - box.size);
    });

    // detect overlapping and join boxes
    overlapping = false;
    i = boxes.length;
    while (i--) {
      if (i > 0) {
        const previousBox = boxes[i - 1];
        const box = boxes[i];
        if (previousBox.pos + previousBox.size > box.pos) {
          // overlapping
          previousBox.size += box.size;
          previousBox.targets = previousBox.targets.concat(box.targets);

          // overflow, shift up
          if (previousBox.pos + previousBox.size > totalH) {
            previousBox.pos = totalH - previousBox.size;
          }
          boxes.splice(i, 1); // removing box
          overlapping = true;
        }
      }
    }
  }

  // step 4: normalize y and adjust x
  i = 0;
  boxes.forEach(function(b) {
    let posInCompositeBox = startY; // middle of the label
    b.targets.forEach(function() {
      half[i].y = b.pos + posInCompositeBox + LINEHEIGHT / 2;
      posInCompositeBox += LINEHEIGHT;
      i++;
    });
  });

  // (x - cx)^2 + (y - cy)^2 = totalR^2
  half.forEach(function(label) {
    const rPow2 = label.r * label.r;
    const dyPow2 = Math.pow(Math.abs(label.y - center.y), 2);
    if (rPow2 < dyPow2) {
      label.x = center.x;
    } else {
      const dx = Math.sqrt(rPow2 - dyPow2);
      if (!isRight) {
        // left
        label.x = center.x - dx;
      } else {
        // right
        label.x = center.x + dx;
      }
    }
    drawLabel(label);
  });
}

就酱紫,图表出来咯

JS制作Antv图表实例

L7(L代表location,7代表7大洲)

经过一次次的失败,我终于画出一个地图了,(┬_┬)

上手案例

先引入js文件(antv的地图是依赖高德地图的,所以一定要申请一个高德地图的key,不过文档中看到一句话:2.0版本在L7内部动态引入了高德地图JS API,因此不再需要单独引入高德JS API,只需设置 type 为 amap 并且传入token,所以我们可以直接使用antv提供的js文件)

<script src="jquery-3.4.1.min.js"></script>
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.l7-1.4.6/build/L7-min.js"></script>
<script src="https://unpkg.com/@antv/l7"></script>

在body中创建放图表的容器

<div id='map' ></div>

js代码

//此处的构造函数Scene和GaodeMap是L7对象上的,所以一定要L7.Scene和L7.GaodeMap
const scene = new L7.Scene({
    id: 'map',
    map:new L7.GaodeMap({
        type: 'amap',
           style: 'dark', // 样式URL
           center: [120.19382669582967, 30.258134],
           pitch: 0,
           zoom: 12,
           token: '高德地图token',
   })
});

新鲜的地图出炉啦

JS制作Antv图表实例

气泡图

html,antv的容器是定位在父级元素上的,所以要改变它的大小,需要给容器加上positive:relative属性。

<div id='map' style='width:800px; height:600px;position: relative;'></div>

引入的外部的js文件跟上面一样,下面是js文件。

const scene = new L7.Scene({
    id: 'map',
    map:new L7.GaodeMap({
        pitch: 0,
    type: 'amap',
    style: 'light',
    center: [ 140.067171, 36.26186 ],
    zoom: 5.32,
    maxZoom: 10
   })
});
fetch(
    'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json'
  )
    .then(res => res.json())
    .then(data => {
      const pointLayer = new L7.PointLayer({})
        .source(data)
        .shape('circle')
        .size('mag', [ 1, 25 ])
        .color('mag', mag => {
          return mag > 4.5 ? '#5B8FF9' : '#5CCEA1';
        })
        .active(true)
        .style({
          opacity: 0.3,
          strokeWidth: 1
        });
  
      scene.addLayer(pointLayer);
    });

气泡图出来啦

JS制作Antv图表实例

转载自:https://juejin.cn/post/6844904065613185037
评论
请登录