在vue中如何封装G2图表
作者:real_忘川
这篇文章主要介绍了在vue中如何封装G2图表,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
vue封装G2图表
<template> <div id="id"></div> </template>
<script> import G2 from '@antv/g2' import { DataSet } from '@antv/data-set' export default { name: 'pie', data () { return { chart: null }; }, props:{ gtwopiedata:{ type: Array }, // gtwopiecolor:{ // type: Array // }, }, methods:{ g2pie(){ if(this.chart){ // 如果存在的话就删除图表再重新生成 this.chart.destroy() } var startAngle = - Math.PI / 2 - Math.PI / 4; var data = this.gtwopiedata.data; var ds = new DataSet(); var dv = ds.createView().source(data); dv.transform({ type: 'percent', field: 'value', dimension: 'type', as: 'percent' }); this.chart = new G2.Chart({ container: 'id', forceFit: true, height: this.gtwopiedata.height, padding: 'auto' }); this.chart.source(dv); this.chart.legend(false); this.chart.coord('theta', { radius: 0.75, innerRadius: 0.5, startAngle: startAngle, endAngle: startAngle + Math.PI * 2 }); this.chart.intervalStack().position('value').color('type', this.gtwopiedata.color).opacity(1).label('percent', { offset: -20, textStyle: { fill: 'white', fontSize: 12, shadowBlur: 2, shadowColor: 'rgba(0, 0, 0, .45)' }, formatter: function formatter(val) { return parseInt(val * 100) + '%'; } }); this.chart.guide().html({ position: ['50%', '50%'], html: '<div class="g2-guide-html"><p class="title">'+this.gtwopiedata.title+'</p></div>' }); this.chart.render(); //draw label var OFFSET = 20; var APPEND_OFFSET = 50; var LINEHEIGHT = 60; var coord = this.chart.get('coord'); // 获取坐标系对象 var center = coord.center; // 极坐标圆心坐标 var r = coord.radius; // 极坐标半径 var canvas = this.chart.get('canvas'); var canvasWidth = this.chart.get('width'); var canvasHeight = this.chart.get('height'); var labelGroup = canvas.addGroup(); var labels = []; // addPieLabel(this.chart); var halves = [[], []]; var data = dv.rows; var angle = startAngle; for (var i = 0; i < data.length; i++) { var percent = data[i].percent; var targetAngle = angle + Math.PI * 2 * percent; var middleAngle = angle + (targetAngle - angle) / 2; angle = targetAngle; var edgePoint = this.getEndPoint(center, middleAngle, r); var routerPoint = this.getEndPoint(center, middleAngle, r + OFFSET); //label var 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 var 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); var startY = center.y - r - OFFSET - LINEHEIGHT; var overlapping = true; var totalH = canvasHeight; var i = void 0; var maxY = 0; var minY = Number.MIN_VALUE; var boxes = half.map(function(label) { var 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) { boxes.forEach(function(box) { var 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) { var previousBox = boxes[i - 1]; var 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) { var 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) { var rPow2 = label.r * label.r; var dyPow2 = Math.pow(Math.abs(label.y - center.y), 2); if (rPow2 < dyPow2) { label.x = center.x; } else { var dx = Math.sqrt(rPow2 - dyPow2); if (!index) { // left label.x = center.x - dx; } else { // right label.x = center.x + dx; } } // drawLabel(label); var _anchor = label._anchor, _router = label._router, fill = label.fill, y = label.y; var labelAttrs = { y: y, fontSize: 12, // 字体大小 fill: '#808080', text: label._data.type + '\n' + label._data.value, textBaseline: 'bottom' }; var lastPoint = { y: 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'; } // 绘制文本 var text = labelGroup.addShape('Text', { attrs: labelAttrs }); labels.push(text); // 绘制连接线 var 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: points, lineWidth: 1, stroke: fill } }); }); }); canvas.draw(); // this.chart.on('afterpaint', function() { // addPieLabel(this.chart); // }); }, // g2获取饼图点位置 getEndPoint(center, angle, r) { return { x: center.x + r * Math.cos(angle), y: center.y + r * Math.sin(angle) }; } }, watch: { gtwopiedata: function (val, oldVal) { // 监听数据,当发生变化时,触发回调函数绘制图表,使用mounted无法正常绘制 // if(this.dothisfun){ this.g2pie(val); // this.dothisfun = false // } } }, // mounted(){ // this.g2pie(); // } } </script>
<style scoped> #id{ width: 100%; height: 100%; } </style>
本来是想将生成的方法封装到js文件中的,但是不知道为什么,import G2 进入js文件之后,vue便会卡在92%无法继续热更新,node的cpu占用率也会饱满,所以只好封装在.vue文件中,以子组件的形式被父组件调用。
本处需要注意的第一个问题,即为data中定义的chart,如果不定义,直接用let chart = new G2.chart(),也确实能够正常生成图表,但是当数据更新的时候,便会重新渲染生成新的图表,此时页面上会同时存在多个图表,所以需要提前定义chart,并使用this.chart = new G2.chart()。
本处需要注意的第二个问题,即为使用mounted钩子函数运行此函数时,因为并未检测到数据变化,所以不会生成有效图表,所以需要使用watch监听数据变化,当发生变化的时候,执行方法渲染图表。
vue引入G2图表
G2 是一套基于图形语法理论的可视化底层引擎,以数据驱动,提供图形语法与交互语法,具有高度的易用性和扩展性。使用 G2,你可以无需关注图表各种繁琐的实现细节,一条语句即可使用 Canvas 或 SVG 构建出各种各样的可交互的统计图表;
线上示例
特性
- 💯 完善的图形语法:数据到图形的映射,能够绘制出所有的图表;
- 🤩 全新的交互语法:通过触发和反馈机制可以组合出各种交互行为,对数据进行探索;
- 🦍 强大的 View 模块:可支持开发个性化的数据多维分析图形;
- 👬 双引擎渲染:Canvas 或 SVG 任意切换;
- 💄 可视化组件体系:面向交互、体验优雅;
- ✨全面拥抱 TypeScript:提供完整的类型定义文件;
介绍一下在vue中使用G2
安装G2依赖:
npm instal @antv/g2
在绘图前我们需要为 G2 准备一个 DOM 容器:
<div id="c1"></div>
执行代码:
import * as G2 from '@antv/g2'; export default { mounted() { const data = [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ]; // Step 1: 创建 Chart 对象 const chart = new G2.Chart({ container: 'c1', // 指定图表容器 ID width: 600, // 指定图表宽度 height: 300, // 指定图表高度 }); // Step 2: 载入数据源 chart.data(data); // Step 3:创建图形语法,绘制柱状图 chart.interval().position('genre*sold'); // Step 4: 渲染图表 chart.render(); } }
效果展示:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。