前端数据可视化第一篇,基础的svg和canvas
SVG是一种基于XML语法的图象格式,全称是可缩放矢量图,其他图像格式都是基于像素处理的,SVG则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。
SVG元素可以直接插入网页,成为DOM的一部分,然后用JavaScript和css进行操作
<!DOCTYPE html>
<html>
<head></head>
<body>
<svg
id="mysvg"
xmlns="https:/www.w3.org/2000/svg"
viewBox="0 0 800 600"
preserveAspectRatio="xMidYMid meet"
>
<circle id="mycircle" cx="400" cy="300" r="50"/>
</svg>
</body>
</html>
api
circle标签
circle标签代表圆形,cx、cy、r属性分别为横坐标、纵坐标、和半径单位为像素。
<svg width="300" height="300">
<circle cx="30" cy="50" r="25" />
<circle cx="30" cy="50" r="25" class="red"/>
<circle cx="30" cy="50" r="25" class="fancy"/>
</svg>
line标签代表直线
<svg width="300" height="300">
<line x1="0" y1="0" x2="200" y2="0" />
</svg>
polyline标签用于绘制一根折线
<svg width="300" height="300">
<polyline points="3,3 30,28 3, 53" fill="none" stroke="black"/>
</svg>
polygon元素定义了一个由一组首尾相连的直线线段构成的闭合多边形形状。最后一点连接到第一点。
rect标签用于绘制矩形
<svg width="300" height="300">
<rect x="0" y="0" height="100" width="200" style="stroke: #70d5dd; fill: #dd524b" />
</svg>
ellipse标签用于绘制椭圆
<svg width="300" height="180"> <ellipse cx="60" cy="60" ry="40" rx="20" stroke="black" stroke-width="5" fill="silver"/></svg>
polygon标签用于绘制多边形
<svg width="300" height="180"> <polygon fill="green" stroke="orange" stroke-width="1" points="0,0 100,0 100, 100 0,100 0, 0"/></svg>
path标签用于绘制路径
<svg width="300" height="300"> <path d=" M 18,3 L 46,3 L 46,40 L 62,40 L 32,68 L 3,40 L 18,40 Z " > </path></svg>
marker-start属性
Marker-end属性
stroke-linecap属性:用于控制描边末端的样式
Stroke-dasharray属性:控制画笔的虚实,通过实线和虚线控制画
stroke-dashoffset属性:相对于绘制的起点偏移的量,正值是向右或者顺时针偏移,负值是向左或者逆时针偏移
text标签用于绘制文本
<svg width="300" height="300"> <text x="50" y="25">22</text></svg>
use标签用于复制一个形状
<svg viewBox="0 0 30 10" xmlns="https://www.w3.org/2000/svg"> <circle id="myCircle" cx="5" cy="5" r="4" /> <use href="#myCircle" x="10" y="0" fill="blue"/> <use href="#myCircle" x="20" y="0" fill="white" stroke="blue" /></svg>
g标签用于将多个形状组成一个组,方便复用
<svg width="300" height="300"> <g id="myCircle"> <text></text> <circle cx="50" cy="50" r="20"/> </g></svg>
defs标签用于自定义形状,它内部的代码不会显示,仅供饮用
<svg width="300" height="100">
<defs>
<g id="myCircle">
<text x="25" y="20"></text>
<circle cx="50" cy="50" r="20"/>
</g>
</defs>
<usr href="#myCircle" x="0" y="0"/> <usr href="#myCircle" x="100" y="0" fill="blue"/> <usr href="#myCircle" x="200" y="0" fill="white" stroke="blue"/>
</svg>
pattern标签用于自定义一个形状,该形状可以被引用来平铺一个区域
<svg width="500" height="500"> <defs> <pattern id="dots" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse"> <circle fill="#bee9e8" cx="50" r="35"/> </pattern> </defs> <rect x="0" y="0" width="100%" height="100%" fill="url(#dots)" /></svg>
image标签用于插入图片文件
<svg viewBox="0 0 100 100" width="100" height"100"> <image xlink:href="path/to/image.jpg" width="50%" height="50%"/></svg>
animate标签用于产生动画效果
<svg width="500px" height="500px"> <rect x="0" y="0" width="100" height="100" fill="#feac5e"> <animate attributeName="x" from="0" to="500" dur="2s" repeatCount="indefinite" /> </rect></svg>
animateTransform
<svg width="500px" height="500px"> <rect x="250" y="250" width="50" height="50" fill="#4bc0c8"> <animateTransform attributeName="transform" type="rotate" begin="0s" dur="10s" from="0 200 200" to="360 400 400" repeat="indefinite" /> </rect></svg>
foreignObject
<foreignObject>
元素允许包含来自不同的 XML 命名空间的元素。在浏览器的上下文中,很可能是 XHTML / HTML
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<style>
polygon { fill: black }
div {
color: white;
font:18px serif;
height: 100%;
overflow: auto;
}
</style>
<polygon points="5,5 195,10 185,185 10,195" />
<!-- Common use case: embed HTML text into SVG -->
<foreignObject x="20" y="20" width="160" height="160">
<!--
In the context of SVG embeded into HTML, the XHTML namespace could
be avoided, but it is mandatory in the context of an SVG document
-->
<div xmlns="http://www.w3.org/1999/xhtml">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed mollis mollis mi ut ultricies. Nullam magna ipsum,
porta vel dui convallis, rutrum imperdiet eros. Aliquam
erat volutpat.
</div>
</foreignObject>
</svg>
在 SVG
中,并非只能填充图形颜色和添加描边,你也可以像 CSS
一样,给绘制的图形应用添加渐变色。分为线性渐变、径向渐变
线性渐变是沿着直线改变颜色
<svg width="300" height="500">
<defs>
<linearGradient id="test">
<stop offset="5%" stop-color="#12c2e9" />
<stop offset="85%" stop-color="#c471ed" />
</linearGradient>
</defs>
<rect fill="url(#test)" x="10" y="10" width="200" height="100"></rect>
</svg>
我们在画布中创建一个 defs
元素,在内部创建一个 linearGradient
标签(该标签用于定义线性渐变,应用于图形元素的填充、描边)。内部放了两个 stop 标签,这两个标签通过指定的位置 offset
属性和 stop-color
属性来说明在渐变的特定位置上渲染指定的颜色。
这里要注意的是 offset
值是从 0
开始的,范围为 0%—100%
(或者是 0—1
),如果出现位置重合,将采用后面设置的值。
stop
标签一共有三个属性,上面我们已经展示了 stop
标签的两个属性,还有一个 stop-opacity
属性,用于设置某个位置的透明度
SVGGElement对应g标签
SVGDefsElement对应defs标签
SVGEllipseElement对应ellipse标签
https://developer.mozilla.org/en-US/docs/Web/API/SVGGElement
<html><body> <svg id="mysvg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet" > <circle id="myCircle" cx="400" cy="300" r="50" /> </svg></body><css type="text/css"> circle { stroke-width: 5; stroke: #f00; fill: #ff0; } circle:hover { stroke: #090; fill: #fff; }<css><script type="text/javascript"> var mycircle = document.getElementById('mycircle'); mycircle.addEventListener('click',function(e) { console.log('circle clicked - enlarging'); mycircle.setAttribute('r',60); },false)</script></html>
作为css背景图
描边动画
<svg width="100%" height="100"> <text text-anchor="middle" x="50%" y="50%" class="text text-1"> segmentfault.com </text> <text text-anchor="middle" x="50%" y="50%" class="text text-2"> segmentfault.com </text> <text text-anchor="middle" x="50%" y="50%" class="text text-3"> segmentfault.com </text> <text text-anchor="middle" x="50%" y="50%" class="text text-4"> segmentfault.com </text></svg><css>.text { font-size: 64px; font-weight: bold; text-transform: uppercase; fill: none; stroke-width: 2px; stroke-dasharray: 90 310; animation: stroke 6s infinite linear;}.text-1 { stroke: #3498db; text-shadow: 0 0 5px #3498db; animation-delay: -1.5s;}.text-2 { stroke: #f39c12; text-shadow: 0 0 5px #f39c12; animation-delay: -3s;}.text-3 { stroke: #e74c3c; text-shadow: 0 0 5px #e74c3c; animation-delay: -4.5s;}.text-4 { stroke: #9b59b6; text-shadow: 0 0 5px #9b59b6; animation-delay: -6s;}@keyframes stroke { 100% { stroke-dashoffset: -400; } }</css>
圆形进度条
<svg> <circle cx="150" cy="73" r="60" stroke="grey" stroke-width="12" fill="none" stroke-dasharray="190" stroke-dashoffset="-190" stroke-linecap="round" /> <circle cx="150" cy="73" r="60" stroke="gold" stroke-width="12" fill="none" stroke-dasharray="95 190" #修改此数字即为进度条进度 stroke-dashoffset="-190" stroke-linecap="round" /></svg>
svg元素可以插入class,用css控制元素
<canvas>
元素用于生成图像。它本身就像一个画布,JavaScript 通过操作它的 API,在上面生成图像。它的底层是一个个像素,基本上<canvas>
是一个可以用 JavaScript 操作的位图(bitmap)
它与 SVG 图像的区别在于,<canvas>
是脚本调用各种方法生成图像,SVG 则是一个 XML 文件,通过各种子元素生成图像。
每个<canvas>
元素都有一个对应的CanvasRenderingContext2D
对象(上下文对象)。Canvas API 就定义在这个对象上面。
<canvas id="myCanvas" width="400" height="250">
您的浏览器不支持 Canvas
</canvas>
<script>
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
</script>
Canvas API 需要getContext
方法指定参数2d
,表示该<canvas>
节点生成 2D 的平面图像。如果参数是webgl
,就表示用于生成 3D 的立体图案,这部分属于 WebGL API。
方法:
绘制路径:
ctx.beginPath()
:
ctx.closePath()
:
ctx.moveTo()
:
ctx.lineTo()
:
ctx.fill()
:
ctx.stroke()
:
ctx.fillStyle()
:
ctx.strokeStyle
:
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
:
ctx.restore()
方法将画布的样式恢复到上一个保存的快照,如果没有已保存的快照,则不产生任何效果。
图像变换:
ctx.rotate
:图像旋转
ctx.scale
:图像缩放
ctx.translate
:图像平移
ctx.transform
:设置图像变换
ctx.settransform
:取消前面的图像变换
如果对性能要求不太高,数据量不太大的绘图需求,优先适用svg,div。比如图中这种坐标比较复杂的图,那就适用svg,如果排列简单,那直接用div+borderRadius也能实现圆的排列,不需要使用svg。如果数据量很大,还要支持各种事件响应,那最好还是选择canvas来做图形渲染。
一般来说,Canvas 更适合绘制图形元素数量较多(这一般是由数据量大导致)的图表(如热力图、地理坐标系或平行坐标系上的大规模线图或散点图等),也利于实现某些视觉 特效。但是,在不少场景中,SVG 具有重要的优势:它的内存占用更低(这对移动端尤其重要)、并且用户使用浏览器内置的缩放功能时不会模糊。
选择哪种渲染器,我们可以根据软硬件环境、数据量、功能需求综合考虑。
在环境较差,出现性能问题需要优化的场景下,可以通过试验来确定使用哪种渲染器。比如有这些经验:
高效率绘制各种美观图表
安装echart
npm install echarts --save
安装完成后 ECharts 和 zrender 会放在 node_modules 目录下
引入echart
import echarts from "echarts"
基本名词解释
名词 | 描述 |
---|---|
chart | 是指一个完整的图表,如折线图,饼图等“基本”图表类型或由基本图表组合而成的“混搭”图表,可能包括坐标轴、图例等 |
axis | 直角坐标系中的一个坐标轴,坐标轴可分为类目型、数值型或时间型 |
xAxis | 直角坐标系中的横轴,通常并默认为类目型 |
yAxis | 直角坐标系中的纵轴,通常并默认为数值型 |
grid | 直角坐标系中除坐标轴外的绘图网格,用于定义直角系整体布局 |
legend | 图例,表述数据和图形的关联 |
dataRange | 值域选择,常用于展现地域数据时选择值域范围 |
dataZoom | 数据区域缩放,常用于展现大量数据时选择可视范围 |
roamController | 缩放漫游组件,搭配地图使用 |
toolbox | 辅助工具箱,辅助功能,如添加标线,框选缩放等 |
tooltip | 气泡提示框,常用于展现更详细的数据 |
timeline | 时间轴,常用于展现同一系列数据在时间维度上的多份数据 |
series | 数据系列,一个图表可能包含多个系列,每一个系列可能包含多个数据 |
图表名词
名词 | 描述 |
---|---|
line | 折线图,堆积折线图,区域图,堆积区域图。 |
bar | 柱形图(纵向),堆积柱形图,条形图(横向),堆积条形图。 |
scatter | 散点图,气泡图。散点图至少需要横纵两个数据,更高维度数据加入时可以映射为颜色或大小,当映射到大小时则为气泡图 |
k | K线图,蜡烛图。常用于展现股票交易数据。 |
pie | 饼图,圆环图。饼图支持两种(半径、面积)南丁格尔玫瑰图模式。 |
radar | 雷达图,填充雷达图。高维度数据展现的常用图表。 |
chord | 和弦图。常用于展现关系数据,外层为圆环图,可体现数据占比关系,内层为各个扇形间相互连接的弦,可体现关系数据 |
force | 力导布局图。常用于展现复杂关系网络聚类布局。 |
map | 地图。内置世界地图、中国及中国34个省市自治区地图数据、可通过标准GeoJson扩展地图类型。支持svg扩展类地图应用,如室内地图、运动场、物件构造等。 |
heatmap | 热力图。用于展现密度分布信息,支持与地图、百度地图插件联合使用。 |
gauge | 仪表盘。用于展现关键指标数据,常见于BI类系统。 |
funnel | 漏斗图。用于展现数据经过筛选、过滤等流程处理后发生的数据变化,常见于BI类系统。 |
evnetRiver | 事件河流图。常用于展示具有时间属性的多个事件,以及事件随时间的演化。 |
treemap | 矩形式树状结构图,简称:矩形树图。用于展示树形数据结构,优势是能最大限度展示节点的尺寸特征。 |
venn | 韦恩图。用于展示集合以及它们的交集。 |
tree | 树图。用于展示树形数据结构各节点的层级关系 |
wordCloud | 词云。词云是关键词的视觉化描述,用于汇总用户生成的标签或一个网站的文字内容 |
实例
//基于准备好的dom,初始化echart图表
var myChart = ec.init(document.getElementById('mainBar'));
//定义图表option
var option = {
//标题,每个图表最多仅有一个标题控件,每个标题控件可设主副标题
title: {
//主标题文本,'\n'指定换行
text: '2013年广州降水量与蒸发量统计报表',
//主标题文本超链接
link: 'http://www.tqyb.com.cn/weatherLive/climateForecast/2014-01-26/157.html',
//副标题文本,'\n'指定换行
subtext: 'www.stepday.com',
//副标题文本超链接
sublink: 'http://www.stepday.com/myblog/?Echarts',
//水平安放位置,默认为左侧,可选为:'center' | 'left' | 'right' | {number}(x坐标,单位px)
x: 'left',
//垂直安放位置,默认为全图顶端,可选为:'top' | 'bottom' | 'center' | {number}(y坐标,单位px)
y: 'top'
},
//提示框,鼠标悬浮交互时的信息提示
tooltip: {
//触发类型,默认('item')数据触发,可选为:'item' | 'axis'
trigger: 'axis'
},
//图例,每个图表最多仅有一个图例
legend: {
//显示策略,可选为:true(显示) | false(隐藏),默认值为true
show: true,
//水平安放位置,默认为全图居中,可选为:'center' | 'left' | 'right' | {number}(x坐标,单位px)
x: 'center',
//垂直安放位置,默认为全图顶端,可选为:'top' | 'bottom' | 'center' | {number}(y坐标,单位px)
y: 'top',
//legend的data: 用于设置图例,data内的字符串数组需要与sereis数组内每一个series的name值对应
data: ['蒸发量','降水量']
},
//工具箱,每个图表最多仅有一个工具箱
toolbox: {
//显示策略,可选为:true(显示) | false(隐藏),默认值为false
show: true,
//启用功能,目前支持feature,工具箱自定义功能回调处理
feature: {
//辅助线标志
mark: {show: true},
//dataZoom,框选区域缩放,自动与存在的dataZoom控件同步,分别是启用,缩放后退
dataZoom: {
show: true,
title: {
dataZoom: '区域缩放',
dataZoomReset: '区域缩放后退'
}
},
//数据视图,打开数据视图,可设置更多属性,readOnly 默认数据视图为只读(即值为true),可指定readOnly为false打开编辑功能
dataView: {show: true, readOnly: true},
//magicType,动态类型切换,支持直角系下的折线图、柱状图、堆积、平铺转换
magicType: {show: true, type: ['line', 'bar']},
//restore,还原,复位原始图表
restore: {show: true},
//saveAsImage,保存图片(IE8-不支持),图片类型默认为'png'
saveAsImage: {show: true}
}
},
//是否启用拖拽重计算特性,默认关闭(即值为false)
calculable: true,
//直角坐标系中横轴数组,数组中每一项代表一条横轴坐标轴,仅有一条时可省略数值
//横轴通常为类目型,但条形图时则横轴为数值型,散点图时则横纵均为数值型
xAxis: [
{
//显示策略,可选为:true(显示) | false(隐藏),默认值为true
show: true,
//坐标轴类型,横轴默认为类目型'category'
type: 'category',
//类目型坐标轴文本标签数组,指定label内容。 数组项通常为文本,'\n'指定换行
data: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
}
],
//直角坐标系中纵轴数组,数组中每一项代表一条纵轴坐标轴,仅有一条时可省略数值
//纵轴通常为数值型,但条形图时则纵轴为类目型
yAxis: [
{
//显示策略,可选为:true(显示) | false(隐藏),默认值为true
show: true,
//坐标轴类型,纵轴默认为数值型'value'
type: 'value',
//分隔区域,默认不显示
splitArea: {show: true}
}
],
//sereis的数据: 用于设置图表数据之用。series是一个对象嵌套的结构;对象内包含对象
series: [
{
//系列名称,如果启用legend,该值将被legend.data索引相关
name: '蒸发量',
//图表类型,必要参数!如为空或不支持类型,则该系列数据不被显示。
type: 'bar',
//系列中的数据内容数组,折线图以及柱状图时数组长度等于所使用类目轴文本标签数组axis.data的长度,并且他们间是一一对应的。数组项通常为数值
data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
//系列中的数据标注内容
markPoint: {
data: [
{type: 'max', name: '最大值'},
{type: 'min', name: '最小值'}
]
},
//系列中的数据标线内容
markLine: {
data: [
{type: 'average', name: '平均值'}
]
}
},
{
//系列名称,如果启用legend,该值将被legend.data索引相关
name: '降水量',
//图表类型,必要参数!如为空或不支持类型,则该系列数据不被显示。
type: 'bar',
//系列中的数据内容数组,折线图以及柱状图时数组长度等于所使用类目轴文本标签数组axis.data的长度,并且他们间是一一对应的。数组项通常为数值
data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
//系列中的数据标注内容
markPoint: {
data: [
{type: 'max', name: '最大值'},
{type: 'min', name: '最小值'}
]
},
//系列中的数据标线内容
markLine: {
data: [
{type: 'average', name: '平均值'}
]
}
}
]
};
//为echarts对象加载数据
myChart.setOption(option);
D3 的全称是(Data-DrivenDocuments),顾名思义是一个被数据驱动的文档。其实是对数据进行可视化的 JavaScript 库。D3 将强大的可视化和交互技术与数据驱动的 DOM 操作方法相结合,能最大限度地使用现代浏览器的性能同时为设计可视化界面保留了最大的自由度。D3 强调 Web 标准,所以无需将自己与专有框架联系起来
ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器,底层依赖轻量级的矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。ECharts 提供完善的中文文档,能够快速通过配置实现支持的图表。
开发流程
对于 ECharts,hightchart 这类经过进一步封装的可视化库开发流程
对于 D3
ECharts,hightchart 这类可视化库,经过配置即可完成图表的绘制,D3 要稍微麻烦一点。
使用 D3 绘图需要
对比 D3 和 ECharts 之前,先对比下两种浏览器图形渲染技术 Canvas 和 SVG 的主要区别,基本上所有的浏览器可视化第三方库都是基于这两种浏览器图形渲染技术实现的(WebGl 虽然和 2D 的 canvas 没啥关系,广义上也算 Canvas):
SVG | Canvas |
---|---|
矢量图不依赖分辨率,放大不失真 | 位图依赖分辨率 |
基于 XML 支持 DOM 事件处理器,SVG 中每个图形节点都是单独的 DOM 节点可以附加 js 事件 | 不支持事件处理器 如果要为细粒度的元素添加事件,就需要边缘检测算法,无疑增加了难度而且不一定能保证十分精确 |
适合带有大型渲染区域的应用(比如:地图(每个节点区域都比较大,节点不是很多)) | 最适合图像密集型的应用比如游戏 能够方便的保存结果图像 |
复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都快不了) | 一旦图形绘制完成,就不会再得到浏览器的关注 如果位置或者大小变化整个区域都要重新绘制 |
可以使用 CSS 文本渲染能力较强 | 文本渲染能力较弱 |
简单对比后:
SVG 比较适合 数据量不是很大,应用存在大量的用户交互场景。
Canvas 比较适合事件交与较少,文本较少,或者数据量大画面刷新快的场景。
ECharts | D3 |
---|---|
Canvas 为主 4.0+ 也支持 SVG | SVG 为主 4.0+ 也支持 Canvas |
提供很多图表通过简单配置可以满足大部分需求 | 不能通过简单配置实现大部分图表 |
基本不可定制 | 自由度很大,基本可以自己绘制任何图表 |
提供完善中文文档,示例功能完善 | 提供完善英文文档,3.x 版本中文文档比较完善,之后翻译的不完善,部分翻译可能需求查看英文方便理解,大部分示例需要完善才能使用,主要是参考价值 |
开发效率高,通过快速配置即可完成 | 大部分图表需要开发 |
大数据量性能较好 | 需要实现时手动优化 例如 virtual DOM |
提供基于 WebGl 的 GL 版实现 3D 图表 | 借助其他库来实现例如:three.js、glMatrix、Sylvester |
ECharts 等提供的图表的确可以满足大部分的需求,遵循了数据可视化的一些经典范式,一切皆可配置。然而,每个不同的行业对于数据可视化都会有一些定制化的需求,希望能以一些带有行业特征的图表向使用者展示数据背后隐藏的秘密,但是 ECharts 这类图形库基本不可定制,而 D3 自由度度很大,基本可以自己绘制任何想要的图形,这类情况的需求可以使用 D3 进行二次开发,定制适合的图表,但是开发成本会稍高。因此,开发中要根据实际情况来判断。无论采用哪种方式开发都要做好二次封装,把实现的图表成可复用的组件。
对于 ECharts 支持的图表:
使用 ECarts 有较大的优势,开发效率高,动画、事件等实现也比较完善,正常情况下基本没有 bug。
对于 Echarts 不支持的图表: