Canvas学习
今天还是熟悉项目代码,看到项目中用到的折线图组件,就很好奇里面内部实现的原理,于是去查了查发现目前市面上流行的ECharts和antv基本上都是基于canvas实现的。于是就去系统学习一下canvas的用法。
1.坐标系统(2D)
canvas的坐标系统如下所示,是一个以左上角为原点,水平向右为X轴,垂直向下为Y轴,坐标单位为像素(px),所以每个点的坐标都是非负整数。
2.创建画布
let canvas = document.getElementById('container'),//获取容器
let ctx = canvas.getContext('2d');//创建2D canvas上下文
3.文字
创建文字,并添置一些样式,写法与css一致。
ctx.shadowOffsetX = 2;//阴影x轴偏置量
ctx.shadowOffsetY = 2;//阴影y轴偏置量
ctx.shadowBlur = 2;//阴影模糊度
ctx.shadowColor = '#ccc';//阴影颜色
ctx.font = '28px Arial';//字体设置(大小,类型)
ctx.fillStyle = '#999';//字体颜色
ctx.fillText('带阴影的文字', 20, 40);//渲染字体
4. 图形
矩形
//左上坐标、宽度、高度
ctx.fillRect(x, y, width, height)//填充矩形
ctx.strokeRect(x, y, width, height)//描边矩形
ctx.clearRect(x, y, width, height)//清除矩形区域
圆形
ctx.beginPath();
// 圆心横坐标,圆心纵坐标,半径,起始和结束角度,是否逆时针绘制
ctx.arc(x, y, r, 0, Math.PI * 2, false);
ctx.fillStyle = 'green';
ctx.fill(); // 填充圆形
ctx.strokeStyle = 'black';//描边颜色
ctx.stroke(); // 描边圆形
线条
ctx.beginPath();
ctx.moveTo(10, 10); // 起点
ctx.lineTo(200, 100); // 终点
ctx.strokeStyle = 'purple';
ctx.stroke(); // 绘制线条
学了这些练习题就来了
拿到这题一开始还有点不知所措,这都没有坐标轴,我该怎么确定每个点的位置,于是经过思考想到了解决思路: 1.首先绘制一个矩形用于承载这个折线图 2.为了确定各个点位的信息,来表示上升和下降的变化趋势,这时我们就需要在心中画出一个坐标轴。根据如下公式:
确定完点的位置,这时只需要将点通过线条连起来,然后在点的附近添加上文字即可。综上所述,最核心的还得是计算出各个点的位置,话不多说上代码。
let data = [
{ high: 35, low: 22 },
{ high: 37, low: 24 },
{ high: 37, low: 25 },
{ high: 34, low: 24 },
{ high: 33, low: 23 }
];
let canvas = document.getElementById('weather-canvas');
// TODO: 绘图
// 400x200
let ctx = canvas.getContext('2d');
ctx.fillStyle = "#fff"
const width = 400;
const height = 200;
ctx.fillRect(0, 0, width, height);
const margin = { top: 30, right: 40, bottom: 30, left: 30 };
// 计算比例尺
const chartWidth = width - margin.left - margin.right;
const chartHeight = height - margin.top - margin.bottom;
const xScale = chartWidth / (data.length - 1); // X 轴比例尺
const yScale = chartHeight / (Math.max(...data.map(d => d.high)) - Math.min(...data.map(d => d.low))); // Y 轴比例尺
// 绘制最高气温折线
ctx.strokeStyle = 'orange';
ctx.lineWidth = 2;
ctx.beginPath();
data.forEach((d, i) => {
const x = margin.left + i * xScale;
const y = margin.top + chartHeight - (d.high - Math.min(...data.map(d => d.low))) * yScale;
if (i === 0) {
ctx.moveTo(x, y);//如果是起点那么直接绘制点
} else {
ctx.lineTo(x, y);//不是起点,那就绘制折线
}
});
ctx.stroke();
// 绘制最低气温折线
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.beginPath();
data.forEach((d, i) => {
const x = margin.left + i * xScale;
const y = margin.top + chartHeight - (d.low - Math.min(...data.map(d => d.low))) * yScale;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
// 绘制数据点及标注
data.forEach((d, i) => {
const x = margin.left + i * xScale;
// 最高气温点
const highY = margin.top + chartHeight - (d.high - Math.min(...data.map(d => d.low))) * yScale;
ctx.fillStyle = 'orange';
ctx.beginPath();
ctx.arc(x, highY, 5, 0, Math.PI * 2);
ctx.fill();
// 最低气温点
const lowY = margin.top + chartHeight - (d.low - Math.min(...data.map(d => d.low))) * yScale;
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(x, lowY, 5, 0, Math.PI * 2);
ctx.fill();
// 标注温度
ctx.fillStyle = '#000';
ctx.font = '16px Arial';
ctx.fillText(`${d.high}℃`, x + 0, highY - 10); // 最高气温标注
ctx.fillText(`${d.low}℃`, x + 0, lowY + 20); // 最低气温标注
});
// 下载:
let download = document.getElementById('weather-download');
download.href = canvas.toDataURL();
得到的效果图如下: