在学习了 G2 源码后,就想着自己写个 chart 加深理解。这次我是用的 G 来实现的。其实用原生的 canvas api 或者其他绘图库都是差不多的。重要是捋一下思路。
导入绘图库 G,它的 API 都会绑定到 window.G
上面.
<link rel="shortcut icon" href="https://gw.alipayobjects.com/zos/antfincdn/yAeuB2%24niG/favicon.png" />
<script src="https://unpkg.com/@antv/g/dist/index.umd.min.js" type="application/javascript">script>
<script src="https://unpkg.com/@antv/g-canvas/dist/index.umd.min.js" type="application/javascript">script>
创建需要渲染的 div
<div>
<div id="app">div>
div>
引入所需对象
const { Group, Circle, Text, Canvas, Line, Rect, CanvasEvent } = window.G;
创建画布
// 创建一个渲染器,这里使用 Canvas2D
const canvasRenderer = new window.G.Canvas2D.Renderer();
// 创建画布
const app = document.getElementById('app');
const canvas = new Canvas({
container: app,
width: options.width,
height: options.height,
renderer: canvasRenderer,
});
类似于 G2,应该要将数据和配置项解耦出来。
const data = [
{ "legend": "新登设备", "x": "2023-03-17", "y": 290 },
{ "legend": "新登设备", "x": "2023-03-18", "y": 371 },
{ "legend": "新登设备", "x": "2023-03-19", "y": 303 },
{ "legend": "新登设备", "x": "2023-03-20", "y": 580 },
{ "legend": "新登设备", "x": "2023-03-21", "y": 671 },
{ "legend": "新登设备", "x": "2023-03-22", "y": 776 },
{ "legend": "新登设备", "x": "2023-03-23", "y": 41340 },
{ "legend": "新登设备", "x": "2023-03-24", "y": 175221 }
]
// 集合管理配置
const options = {
width: 1000,
height: 600,
padding: 30,
lineWidth: 2,
tickLength: 5,
color: '#DCDFE6',
}
// 绘制边框
const rect = new Rect({
style: {
x: 0,
y: 0,
width: options.width,
height: options.height,
stroke: options.color,
lineWidth: options.lineWidth,
}
})
canvas.appendChild(rect)
// 绘制横坐标
const xAxis = new Line({
style: {
x1: options.padding,
y1: options.height - options.padding,
x2: options.width - options.padding,
y2: options.height - options.padding,
stroke: options.color,
lineWidth: options.lineWidth,
}
})
canvas.appendChild(xAxis);
// 绘制纵坐标
const yAxis = new Line({
style: {
x1: options.padding,
y1: options.padding,
x2: options.padding,
y2: options.height - options.padding,
stroke: options.color,
lineWidth: options.lineWidth,
}
})
canvas.appendChild(yAxis);
// 绘制横坐标刻度
const step = (options.width - (2 * options.padding)) / (data.length + 1)
for (let i = 0; i < data.length; i++) {
const item = data[i];
const tickLine = new Line({
style: {
x1: options.padding + (step * (i + 1)),
y1: options.height - options.padding,
x2: options.padding + (step * (i + 1)),
y2: options.height - options.padding - options.tickLength,
stroke: options.color,
lineWidth: options.lineWidth,
}
})
canvas.appendChild(tickLine);
const tickText = new Text({
style: {
x: options.padding + (step * (i + 1)) - 10,
y: options.height - options.padding + 20,
text: item.x.slice(8),
fill: '#303133',
fontSize: 12,
}
})
canvas.appendChild(tickText);
}
// 绘制纵坐标刻度
const valueStep = Math.max(...data.map(item => item.y)) * 1.1 / 6
const positionStep = (options.height - (2 * options.padding) - 10) / 6
for (let i = 0; i < 7; i++) {
const y = positionStep * (i)
const tickLine = new Line({
style: {
x1: options.padding,
y1: options.height - options.padding - y,
x2: options.padding + options.tickLength,
y2: options.height - options.padding - y,
stroke: options.color,
lineWidth: options.lineWidth,
}
})
canvas.appendChild(tickLine);
const gridLine = new Line({
style: {
x1: options.padding,
y1: options.height - options.padding - y,
x2: options.width - options.padding,
y2: options.height - options.padding - y,
stroke: '#EBEEF5',
lineWidth: 2,
}
})
canvas.appendChild(gridLine);
const tickText = new Text({
style: {
x: options.padding - 10,
y: options.height - options.padding - y + 3,
fill: '#303133',
fontSize: 12,
textAlign: 'right',
text: parseInt(valueStep * i / 10000),
}
})
canvas.appendChild(tickText);
}
已知横坐标系和纵坐标系,那么就可以很好的计算出每个数据点在图表上的位置了。再将他们通过线段连接起来就实现了折线图。
// 绘制折线图
for (let i = 0; i < data.length; i++) {
const item = data[i];
const x = options.padding + (step * (i + 1))
const y = options.height - options.padding - (item.y / valueStep) * positionStep
if (i < data.length - 1) {
const nextItem = data[i + 1];
const nx = options.padding + (step * (i + 2))
const ny = options.height - options.padding - (nextItem.y / valueStep) * positionStep
const line = new Line({
style: {
x1: x,
y1: y,
x2: nx,
y2: ny,
stroke: '#409EFF',
lineWidth: options.lineWidth * 2,
}
})
canvas.appendChild(line);
}
const circle = new Circle({
style: {
cx: x,
cy: y,
r: 2,
lineWidth: 1,
fill: '#FFFFFF',
stroke: '#409EFF'
}
})
canvas.appendChild(circle)
}