引言
数据可视化是将复杂数据以图形化方式呈现的一门艺术,它可以帮助我们更直观地理解数据。 在上一篇中,我们整理了 Canvas 的使用手册,现在我们用一个折线图表来对 API 进行实际使用,为了加深肌肉记忆
案例介绍截图
- 本案例的目标是绘制一个展示每月跑步里程趋势的折线图。使用 Canvas API 来绘制图形、设置样式、处理数据点,并添加坐标轴和数据点标注。
- 基于数据驱动视图理念,先配置数据
- 设置边距配置对象,让图表自适应
- 文章末尾有完整代码
const dataPoints = [
{ month: '一月', value: 50 },
{ month: '二月', value: 450 },
{ month: '三月', value: 300 },
{ month: '四月', value: 900 },
{ month: '五月', value: 300 },
{ month: '六月', value: 250 },
{ month: '七月', value: 600 },
]
let padding = { top: 50, bottom: 20, left: 50, right: 10 }
let title = '每月跑步里程趋势'
DOM 创建
首先,创建一个 HTML 文件,并在<head>标签内设置字符集和标题,在<body>标签内定义一个<canvas>元素,并为其添加样式。
<canvas id="lineChart" width="800" height="400"></canvas>
绘制标题
在 Canvas 上绘制标题,使用fillText方法,并设置字体和颜色。
ctx.font = '20px Arial'
ctx.fillStyle = '#ff00ff'
ctx.fillText(title, 300, 40)
数据准备
定义一个包含月份和对应数值的对象数组,这些数据点将用于绘制折线图。
const dataPoints = [
{ month: '一月', value: 50 },
// ... 其他数据点
]
坐标轴和刻度设置
计算坐标轴的刻度和标签,使用map方法从数据点生成 X 轴标签,为 Y 轴创建刻度。
const xAxisLabel = dataPoints.map(point => point.month)
// ...
const yAxisLabel = new Array(yItem + 1).fill().map((_, i) => Math.ceil((yMax / yItem) * i))
绘制坐标轴
使用moveTo和lineTo方法绘制坐标轴,并设置线条样式。
ctx.beginPath()
// 绘制X轴和Y轴
ctx.moveTo(padding.left, padding.top)
// ...
ctx.strokeStyle = '#000'
ctx.stroke()
绘制刻度线和标签
为 X 轴和 Y 轴绘制刻度线,并添加标签文本。
// X轴刻度线和标签
ctx.moveTo(xPosition, canvas.height - padding.bottom)
ctx.lineTo(xPosition, canvas.height - padding.bottom - lineHeight)
ctx.strokeText(point.month, xPosition, canvas.height - padding.bottom + 15)
// Y轴刻度线和标签
ctx.moveTo(padding.left, yPosition)
ctx.lineTo(padding.left - lineHeight, yPosition)
ctx.strokeText(label, padding.left - 25, yPosition)
折线图绘制
遍历数据点,使用beginPath、moveTo、lineTo和stroke方法绘制折线。
ctx.beginPath()
ctx.strokeStyle = '#00f'
ctx.lineWidth = 2
// ...
ctx.moveTo(padding.left, ydot)
// ...
ctx.lineTo(x, y)
// ...
ctx.stroke()
数据点绘制
在折线图上绘制每个数据点,使用arc方法绘制圆形。
ctx.fillStyle = '#f00'
dataPoints.forEach((point, index) => {
// ...
ctx.beginPath()
ctx.arc(x, y, 5, 0, 2 * Math.PI)
ctx.fill()
})
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Canvas 折线图示例</title>
<style>
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="lineChart" width="800" height="400"></canvas>
<script>
document.addEventListener('DOMContentLoaded', function () {
const canvas = document.getElementById('lineChart')
const ctx = canvas.getContext('2d')
// 数据设置
const dataPoints = [
{ month: '一月', value: 50 },
{ month: '二月', value: 450 },
{ month: '三月', value: 300 },
{ month: '四月', value: 900 },
{ month: '五月', value: 300 },
{ month: '六月', value: 250 },
{ month: '七月', value: 600 },
]
let padding = { top: 50, bottom: 20, left: 50, right: 10 }
let title = '每月跑步里程趋势'
// 绘制标题
ctx.save()
ctx.font = '20px Arial'
ctx.fillStyle = '#ff00ff'
ctx.fillText(title, 300, 40)
ctx.restore()
// 设置刻度和标签
const xAxisLabel = dataPoints.map(point => point.month)
const xScale = (canvas.width - padding.left - padding.right) / dataPoints.length
let yItem = 7
let yHeight = canvas.height - padding.top - padding.bottom
let yMax = Math.max(...dataPoints.map(dp => dp.value))
const ySpace = yHeight / yItem
const yAxisLabel = new Array(yItem + 1).fill().map((_, i) => Math.ceil((yMax / yItem) * i))
const yScale = canvas.height / Math.max(...dataPoints.map(dp => dp.value))
let lineHeight = 15
// 绘制坐标轴
ctx.beginPath()
ctx.moveTo(padding.left, padding.top)
ctx.lineTo(padding.left, canvas.height - padding.bottom)
ctx.lineTo(canvas.width - padding.right, canvas.height - padding.bottom)
ctx.strokeStyle = '#000'
ctx.stroke()
// 绘制 X 轴刻度线和标签
ctx.textBaseline = 'bottom'
ctx.textAlign = 'center'
dataPoints.forEach((point, index) => {
const xPosition = padding.left + index * xScale
ctx.moveTo(xPosition, canvas.height - padding.bottom)
ctx.lineTo(xPosition, canvas.height - padding.bottom - lineHeight) // 高度15的线段
ctx.strokeText(point.month, xPosition, canvas.height - padding.bottom + 15)
})
// 绘制 Y 轴刻度线和标签
ctx.textBaseline = 'middle'
ctx.textAlign = 'right'
yAxisLabel.forEach((label, index) => {
const yPosition = canvas.height - padding.bottom - index * ySpace // 底部留了 padding.bottom
ctx.moveTo(padding.left, yPosition)
ctx.lineTo(padding.left - lineHeight, yPosition)
ctx.strokeText(label, padding.left - 25, yPosition)
})
ctx.stroke()
const getYDot = val => {
return canvas.height - yHeight * (val / yMax) - padding.bottom
}
// 绘制折线图
ctx.beginPath()
ctx.strokeStyle = '#00f'
ctx.lineWidth = 2
let ydot = getYDot(dataPoints[0].value)
ctx.moveTo(padding.left, ydot)
dataPoints.forEach((point, index) => {
let ydot = getYDot(point.value)
const x = padding.left + index * xScale
const y = ydot
console.log(x, y)
ctx.lineTo(x, y)
})
ctx.stroke()
// 绘制数据点
ctx.fillStyle = '#f00'
dataPoints.forEach((point, index) => {
const x = padding.left + index * xScale
const y = getYDot(point.value)
ctx.beginPath()
ctx.arc(x, y, 5, 0, 2 * Math.PI)
ctx.fill()
})
})
</script>
</body>
</html>
结语
通过本案例的实践,我们不仅掌握了 Canvas 的基本使用方法,还学习了如何将数据点转换为可视化图形。折线图作为一种常见的数据可视化形式,能够帮助我们快速理解数据随时间变化的趋势。Canvas 的强大功能,结合 JavaScript 的灵活性,为数据可视化提供了无限可能。