不折腾的前端不是好前端,今天来折腾下Antv图表,对比highcharts这个文档确实不怎么人性化,但是没办法么,毕竟highCharts还是蛮贵的,使用antv之前建议大家看下语雀上的文档。
G2
上手案例
先引入antv的js文件
在body中创建放图表的容器
在页面或者另一个单独的js文件中写图表的渲染代码,我这边是单独写的一个js文件,然后页面通过
引入
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文件
css
html,因为forceFit设置的true随着父级的变化而变化,所以我在外面套了个有长宽的div,当然也可以forceFit设置为false,给容器设置宽高,都可以
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: '总计
19670
'
});
//渲染图表
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);
});
}
就酱紫,图表出来咯
L7(L代表location,7代表7大洲)
经过一次次的失败,我终于画出一个地图了,(┬_┬)
上手案例
先引入js文件(antv的地图是依赖高德地图的,所以一定要申请一个高德地图的key,不过文档中看到一句话:2.0版本在L7内部动态引入了高德地图JS API,因此不再需要单独引入高德JS API,只需设置 type 为 amap 并且传入token,所以我们可以直接使用antv提供的js文件)
在body中创建放图表的容器
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',
})
});
新鲜的地图出炉啦
气泡图
html,antv的容器是定位在父级元素上的,所以要改变它的大小,需要给容器加上positive:relative属性。
引入的外部的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);
});
气泡图出来啦