说明
【跟月影学可视化】学习笔记。
如何用 Canvas 绘制几何图形?
1. Canvas 元素和 2D 上下文
Canvas 元素上的 width 和 height 属性不等同于 Canvas 元素的 CSS 样式的属性。这样分开能更方便地适配不同的显示设备。
CSS 属性中的宽高影响 Canvas 在页面上呈现的大小
HTML 属性中的宽高则决定了 Canvas 的坐标系
Canvas 的 HTML 属性宽高为画布宽高,CSS 样式宽高为样式宽高。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>如何用Canvas绘制层次关系图</title> <style> canvas { width: 256px; height: 256px; } </style> </head> <body> <canvas width="512" height="512"></canvas> </body> </html>
2. Canvas 的坐标系
默认左上角为坐标原点,x 轴水平向右,y 轴垂直向下。
3. 利用 Canvas 绘制几何图形
3.1、获取 Canvas 上下文
首先是获取 Canvas 元素。
const canvas = document.querySelector("canvas");
通过 getContext 方法拿到它的上下文对象。
const context = canvas.getContext("2d");
3.2、用 Canvas 上下文绘制图形
context 对象 API
- 设置状态的 API,可以设置或改变当前的绘图状态(颜色、线宽、坐标变换等)。
- 绘制指令 API,用来绘制不同形状的几何图形。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>如何用Canvas绘制层次关系图</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script> const canvas = document.querySelector("canvas"); const context = canvas.getContext("2d"); /*****方法一:直接改变要绘制的图形顶点的坐标位置*****/ // 将正方形填充成绿色 context.fillStyle = "green"; // 绘制的路径 context.beginPath(); // 调用 rect 指令完成绘图 context.rect(canvas.width/2 - 50, canvas.height/2 - 50, 100, 100); // 将绘制的内容真正输出到画布中 context.fill(); /*****方法二:对 Canvas 画布的整体做一个平移操作*****/ // 将正方形填充成橙红色 context.fillStyle = "salmon"; // 绘制的路径 context.beginPath(); // 暂存状态 context.save(); // 平移 context.translate(-25, -25); // 调用 rect 指令完成绘图 context.rect(canvas.width/2, canvas.height/2, 50, 50); // 恢复状态 context.restore(); // 将绘制的内容真正输出到画布中 context.fill(); </script> </body> </html>
如何用 Canvas 绘制层次关系图?
需要实现下面的效果
json数据如下:
export default { "name": "中国", "children": [ { "name": "浙江", "children": [ { "name": "杭州" }, { "name": "宁波" }, { "name": "温州" }, { "name": "绍兴" } ] }, { "name": "广西", "children": [ { "name": "桂林" }, { "name": "南宁" }, { "name": "柳州" }, { "name": "防城港" } ] }, { "name": "黑龙江", "children": [ { "name": "哈尔滨" }, { "name": "齐齐哈尔" }, { "name": "牡丹江" }, { "name": "大庆" } ] }, { "name": "新疆", "children": [ { "name": "乌鲁木齐" }, { "name": "克拉玛依" }, { "name": "吐鲁番" }, { "name": "哈密" } ] }, { "name": "河北", "children": [ { "name": "石家庄" }, { "name": "唐山" }, { "name": "邯郸" }, { "name": "秦皇岛" } ] }, { "name": "西藏", "children": [ { "name": "拉萨" }, { "name": "昌都" }, { "name": "林芝" } ] }, { "name": "江苏", "children": [ { "name": "南京" }, { "name": "无锡" }, { "name": "徐州" }, { "name": "常州" }, { "name": "连云港" }, { "name": "淮安" } ] }, { "name": "江苏", "children": [ { "name": "南京" }, { "name": "无锡" }, { "name": "徐州" }, { "name": "常州" }, { "name": "连云港" }, { "name": "淮安" } ] }, { "name": "湖南", "children": [ { "name": "长沙" }, { "name": "株洲" }, { "name": "湘潭" }, { "name": "衡阳" }, { "name": "邵阳" }, { "name": "岳阳" } ] }, { "name": "海南", "children": [ { "name": "海口" }, { "name": "三亚" }, { "name": "三沙" } ] }, { "name": "陕西", "children": [ { "name": "西安" }, { "name": "咸阳" }, { "name": "汉中" }, { "name": "安康" }, { "name": "榆林" }, { "name": "延安" } ] }, { "name": "甘肃", "children": [ { "name": "兰州" }, { "name": "酒泉" }, { "name": "金昌" }, { "name": "天水" }, { "name": "嘉峪关" }, { "name": "武威" } ] } ] }
这里使用 d3-hierarchy
工具将数据转为下面的效果
<script src="https://d3js.org/d3-hierarchy.v1.min.js"></script>
{ data: {name: '中国', children: [...]}, children: [ { data: {name: '江苏', children: [...]}, value: 7, r: 186.00172579386546, x: 586.5048250548921, y: 748.2441892254667, } ... ], value: 69, x: 800, y: 800, r: 800, }
代码实现:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>如何用Canvas绘制层次关系图(2)</title> <style> canvas { width: 800px; height: 800px; } </style> </head> <body> <canvas width="1600" height="1600"></canvas> <script src="https://d3js.org/d3-hierarchy.v1.min.js"></script> <script type="module"> import dataSource from './data/map.js'; // 用 d3.hierarchy(data).sum(…).sort(…) 将省份数据按照包含城市的数量,从多到少排序 const regions = d3.hierarchy(dataSource) .sum(d => 1) .sort((a, b) => b.value - a.value); // 通过 d3.pack() 将数据映射为一组 1600 宽高范围内的圆形,留3px的padding const pack = d3.pack() .size([1600, 1600]) .padding(3); const root = pack(regions); console.log(root) const canvas = document.querySelector('canvas'); const context = canvas.getContext('2d'); function draw(ctx, node, { fillStyle = 'rgba(0, 0, 0, 0.2)', textColor = 'white' } = {}) { const children = node.children; const { x, y, r } = node; ctx.fillStyle = fillStyle; ctx.beginPath(); // arc 方法的五个参数分别是圆心的 x、y 坐标、半径 r、起始角度和结束角度 ctx.arc(x, y, r, 0, 2 * Math.PI); ctx.fill(); if (children) { // 遍历子节点绘制 for (let i = 0; i < children.length; i++) { draw(context, children[i]); } } else { // 绘制文本 ctx.fillStyle = textColor; ctx.font = '1.5rem Arial'; ctx.textAlign = 'center'; ctx.fillText(node.data.name, x, y); } } draw(context, root); </script> </body> </html>
Canvas 有哪些优缺点?
Canvas 在 HTML 层面上是一个独立的画布元素,所以所有的绘制内容都是在内部通过绘图指令来完成的,绘制出的图形对于浏览器来说,只是 Canvas 中的一个个像素点,很难直接抽取其中的图形对象进行操作。