赞
踩
基本概念
::: tip
数据可视化,是关于数据视觉表现形式的科学技术研究
:::
这个概念向我们传达了两个信息:
数据可视化简单理解,就是将数据转换成易于人员辨识和理解的视觉表现形式,如各种 2D 图表、3D 图表、地图、矢量图等等,随着技术的不断进步,数据可视化的边界也在不断扩大
数据可视化起源于 20 世纪 60 年代诞生的计算机图形学
::: tip
计算机图形学(Computer Graphics,简称CG)是一种使用数学算法将二维或三维图形转化为计算机显示器的栅格形式的科学
:::
计算机图形学广泛应用于各个领域,深刻影响和改变着我们的生活。
如果你想学习计算机图形学,可以从这里入门
在获得计算机图形学发展后,先后经历了科学可视化、信息可视化和数据可视化三个阶段,最初由科研人员提出科学建模和数据的可视化需求,在进入 20 世纪 90 年代后,出现大量单机数据可视化需求,EXCEL 是这个时期的代表,互联网时代各种产品兴起,大数据爆发式增长,促使数据可视化技术飞速发展
Excel
XMind
Visio
OminiGraffle
让我们通过一组招聘信息来看看具备了数据可视化技能有多受欢迎
前端数据可视化解决方案如下:
Skia 是 Chrome 和 Android 的底层 2D 绘图引擎,具体可参考百度百科,Skia 采用 C++ 编程,由于它位于浏览器的更底层,所以我们平常接触较少
对底层绘图感兴趣的同学可以从这个案例入手,了解一下 C++ 的可视化编程。
OpenGL(Open Graphics Library)是2D、3D图形渲染库,它可以绘制从简单的2D图形到复杂的3D景象。OpenGL 常用于 CAD、VR、数据可视化和游戏等众多领域。
Chrome 使用 Skia 作为绘图引擎,向上层开放了 canvas、svg、WebGL、HTML 等绘图能力。
canvas
是 HTML5 的新特性,它允许我们使用 canvas
元素在网页上通过 JavaScript 绘制图像。
<canvas>
标签只是图形容器,相当于一个画布,canvas
元素本身是没有绘图能力的。所有的绘制工作必须在 JavaScript 内部完成,相当于使用画笔在画布上画画。
注意:必须指定宽高
<canvas id="charts" width="800" height="400"></canvas>
context
是一个封装了很多绘图功能的对象,我们在页面中创建一个 canvas
标签之后,首先要使用 getContext()
获取 canvas
的上下文环境,目前 getContext()
的参数只有 2d,暂时还不支持 3d
getContext("2d")
对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
const canvas = document.getElementById('charts');
const context = canvas.getContext('2d');//--->画笔
moveTo(x, y)
:把路径移动到画布中的指定点,不创建线条lineTo(x, y)
:添加一个新点,然后在画布中创建从该点到最后指定点的线条context.strokeStyle = 'yellowgreen';
context.moveTo(0, 0);
context.lineTo(100, 100);
context.stroke();
fillRect(x, y, width, height)
绘制填充颜色的矩形strokeRect(x, y, width, height)
绘制线条的矩形context.fillStyle = "pink";
context.strokeStyle = "darkred";
context.fillRect(0, 0, 100, 100);
context.strokeRect(120, 0, 100, 100);
arc(x, y, radius, starAngle, endAngle, anticlockwise)
context.beginPath();
context.arc(300, 350, 100, 0, Math.PI * 2, true);
//不关闭路径路径会一直保留下去
context.closePath();
context.fillStyle = 'rgba(0,255,0,0.25)';
context.fill();
// 或
// context.stroke(); // 此时就会有问题
清除绘画的路径,多个图形就不会连接在一起
context.beginPath()
context.closePath()
当在一个画布反复绘制图形,需要将上一次的图形清空
clearRect(x, y, width, height)
fillText(text, x, y, maxWidth)
::: tip
一个少女心满满的例子带你入门 Canvas
:::
SVG是一种基于 XML 的图像文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形
你可以深入 SVG 复杂的细节,但这对我们即将创建的图标不是必须的。以下列表涵盖了我们将用到的构建块。
<svg>
包裹并定义整个矢量图。<svg>
标签之于矢量图就如同 <html>
标签之于一个 web 页面。
<line>
创建一条直线。
<polyline>
创建折线。
<rect>
创建矩形。
<circle>
创建圆。
<ellipse>
创建圆和椭圆。
<polygon>
创建多边形。
<path>
通过指定点以及点和点之间的线来创建任意形状。
所有标签都要包裹在 <svg>
中使用
<line>
<!--
x1 y1 是第一个点坐标
x2 y2 是第二个点坐标
-->
<line x1="" y1="" x2="" y2=""></line>
<polyline>
<!--
依次传入点坐标,即可绘制
-->
<polyline points="
x1 y1
x2 y2
x3 y3
...
"></polyline>
<!-- 你也可以把上面的代码写成: -->
<polyline points="x1 y1, x2 y2, x3 y3"></polyline>
<!-- 或 -->
<polyline points="x1 y1 x2 y2 x3 y3"></polyline>
<rect>
<!--
x y 左上角点坐标
width 宽度
height 高度
-->
<rect x="" y="" width="" height=""></rect>
<circle>
<!--
cx cy 圆心点坐标
r 半径
style 样式
-->
<circle cx='70' cy='95' r='50' style='stroke:black; fill:none'></circle>
<ellipse>
<!--
cx cy 圆心点坐标
rx x轴半径
ry y轴半径
-->
<ellipse cx="" cy="" rx="" ry="" style="fill:black;"></ellipse>
<polygon>
<polygon points="x1 y1, x2 y2, x3 y3" />
<path>
<!--
M 移动到初始位置
L 画线
Z 将结束和开始点闭合
-->
<path d="
M x1 y1
L x2 y2
L x3 y3
L x4 y4
L x5 y5
L x6 y6
L x7 y7
Z
"></path>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Hand Coded SVG</title> <style> html, body { height: 100%; width: 100%; background: #e9e9e9; } body { margin: 0; text-align: center; } .grid { width: 750px; height: 500px; margin: 0 auto; padding-top: 100px; padding-left: 100px; background-image: url('grid.png'); position: relative; } .grid::before { content: ""; border-left: 1px solid #7c7cea; position: absolute; top: 0; left: 100px; width: 750px; height: 600px; } .grid::after { content: ""; border-top: 1px solid #7c7cea; position: absolute; top: 100px; left: 0; width: 850px; height: 500px; } svg { stroke: #000; stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; fill: none; } </style> </head> <body> <div class="grid"> </div> </body> </html>
::: tip
::: details
<!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) chart.setOption({ title: { text: '快速入门ECharts开发' }, xAxis: { data: ['食品', '数码', '服饰', '箱包'] }, yAxis: {}, series: { type: 'bar', data: [100, 120, 90, 150] } }) </script> </body> </html>
:::
思考:ECharts 的绘图流程是怎样的?
::: details
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 300px; } #chart2 { width: 800px; height: 300px; } </style> </head> <body> <div>这是第一个 echarts 图表</div> <div id="chart"></div> <div>这是第二个 echarts 图表</div> <div id="chart2"></div> <script> const chartDom = document.getElementById('chart') const chartDom2 = document.getElementById('chart2') const chart = echarts.init(chartDom) const chart2 = echarts.init(chartDom2) const option1 = { xAxis: { type: 'category', boundaryGap: false, data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'line', areaStyle: {} }] }; const option2 = { legend: { data: ['高度(km)与气温(°C)变化关系'] }, tooltip: { trigger: 'axis', formatter: 'Temperature : <br/>{b}km : {c}°C' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'value', axisLabel: { formatter: '{value} °C' } }, yAxis: { type: 'category', axisLine: {onZero: false}, axisLabel: { formatter: '{value} km' }, boundaryGap: false, data: ['0', '10', '20', '30', '40', '50', '60', '70', '80'] }, series: [{ name: '高度(km)与气温(°C)变化关系', type: 'line', smooth: true, lineStyle: { width: 3, shadowColor: 'rgba(0,0,0,0.4)', shadowBlur: 10, shadowOffsetY: 10 }, data:[15, -50, -56.5, -46.5, -22.1, -2.5, -27.7, -55.7, -76.5] }] } chart.setOption(option1) chart2.setOption(option2) </script> </body> </html>
:::
系列(series)是指:一组数值映射成对应的图
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) const option = { xAxis: { data: ['一季度', '二季度', '三季度', '四季度'] }, yAxis: {}, series: [{ type: 'pie', center: ['65%', 60], radius: 35, data: [{ name: '分类1', value: 50 }, { name: '分类2', value: 60 }, { name: '分类3', value: 55 }, { name: '分类4', value: 70 }] }, { type: 'line', data: [100, 112, 96, 123] }, { type: 'bar', data: [79, 81, 88, 72] }] } chart.setOption(option) </script> </body> </html>
:::
ECharts 4 开始支持了 数据集(dataset)组件用于单独的数据集声明,从而数据可以单独管理,被多个组件复用,并且可以自由指定数据到视觉的映射。这一特性能将逻辑和数据分离,带来更好的复用,并易于理解。
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) const option = { xAxis: { type: 'category' }, yAxis: {}, dataset: { source: [ ['一季度', 79, 100, '分类1', 50], ['二季度', 81, 112, '分类2', 60], ['三季度', 88, 96, '分类3', 55], ['四季度', 72, 123, '分类4', 70], ] }, series: [{ type: 'pie', center: ['65%', 60], radius: 35, encode: { itemName: 3, value: 4 } }, { type: 'line', encode: { x: 0, y: 2 } }, { type: 'bar', encode: { x: 0, y: 1 } }] } chart.setOption(option) </script> </body> </html>
:::
ECharts 中除了绘图之外其他部分,都可抽象为 「组件」。例如,ECharts 中至少有这些组件:xAxis(直角坐标系 X 轴)、yAxis(直角坐标系 Y 轴)、grid(直角坐标系底板)、angleAxis(极坐标系角度轴)…
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) const option = { title: { text: '数据可视化', subtext: '慕课网数据可视化体系课' }, xAxis: { type: 'category' }, yAxis: {}, legend: { data: [{ name: '分类', // 强制设置图形为圆。 icon: 'circle', // 设置文本为红色 textStyle: { color: 'red' } }, '折线图', '柱状图'], left: 100 }, toolbox: { feature: { dataZoom: { yAxisIndex: 'none' }, restore: {}, saveAsImage: {} } }, dataZoom: [{ show: true, start: 30, end: 70 }], dataset: { source: [ ['一季度', 79, 100, '分类1', 50], ['二季度', 81, 112, '分类2', 60], ['三季度', 88, 96, '分类3', 55], ['四季度', 72, 123, '分类4', 70], ] }, grid: [{ left: 50, top: 70 }], series: [{ name: '分类', type: 'pie', center: ['65%', 60], radius: 35, encode: { itemName: 3, value: 4 } }, { name: '折线图', type: 'line', encode: { x: 0, y: 2 } }, { name: '柱状图', type: 'bar', encode: { x: 0, y: 1 } }] } chart.setOption(option) </script> </body> </html>
:::
大多数组件都提供了定位属性,我们可以采用类似 CSS absolute 的定位属性来控制组件的位置,下面这个案例可以通过修改 grid 组件定位来控制图表的位置
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; margin-top: 10px; } </style> </head> <body> <div> top: <input type="text" id="top"> left: <input type="text" id="left"> right: <input type="text" id="right"> bottom: <input type="text" id="bottom"> </div> <div id="chart"></div> <script> let _left = 0 let _top = 0 let _bottom = 0 let _right = 0 const topInput = document.getElementById('top') const leftInput = document.getElementById('left') const bottomInput = document.getElementById('bottom') const rightInput = document.getElementById('right') const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) function addInputEvent(dom, key) { dom.addEventListener('input', function(e) { value = e.target.value switch(key) { case 'top': _top = value break case 'left': _left = value break case 'bottom': _bottom = value break case 'right': _right = value break } render() }) } function render() { const option = { title: { text: '数据可视化', subtext: '慕课网数据可视化体系课' }, xAxis: { type: 'category' }, yAxis: {}, dataset: { source: [ ['一季度', 79, 100, '分类1', 50], ['二季度', 81, 112, '分类2', 60], ['三季度', 88, 96, '分类3', 55], ['四季度', 72, 123, '分类4', 70], ] }, grid: [{ left: _left, top: _top, right: _right, bottom: _bottom }], series: [{ name: '折线图', type: 'line', encode: { x: 0, y: 2 } }] } chart.setOption(option) } window.onload = function() { topInput.value = _top leftInput.value = _left bottomInput.value = _bottom rightInput.value = _right addInputEvent(topInput, 'top') addInputEvent(leftInput, 'left') addInputEvent(bottomInput, 'bottom') addInputEvent(rightInput, 'right') render() } </script> </body> </html>
:::
很多系列,例如 line(折线图)、bar(柱状图)、scatter(散点图)、heatmap(热力图)等等,需要运行在 “坐标系” 上。坐标系用于布局这些图,以及显示数据的刻度等等。例如 ECharts 中至少支持这些坐标系:直角坐标系、极坐标系、地理坐标系(GEO)、单轴坐标系、日历坐标系 等。其他一些系列,例如 pie(饼图)、tree(树图)等等,并不依赖坐标系,能独立存在。还有一些图,例如 graph(关系图)等,既能独立存在,也能布局在坐标系中,依据用户的设定而来。
一个坐标系,可能由多个组件协作而成。我们以最常见的直角坐标系来举例。直角坐标系中,包括有 xAxis(直角坐标系 X 轴)、yAxis(直角坐标系 Y 轴)、grid(直角坐标系底板)三种组件。xAxis、yAxis 被 grid 自动引用并组织起来,共同工作。
我们来看下图,这是最简单的使用直角坐标系的方式:只声明了 xAxis、yAxis 和一个 scatter(散点图系列),ECharts 会为它们创建 grid 并进行关联:
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) const option = { xAxis: {}, yAxis: {}, dataset: { source: [ [13, 44], [51, 51], [51, 32], [67, 19], [19, 33] ] }, series: [{ type: 'scatter', encode: { x: 0, y: 1 } }] } chart.setOption(option) </script> </body> </html>
:::
再来看下图,两个 yAxis,共享了一个 xAxis。两个 series,也共享了这个 xAxis,但是分别使用不同的 yAxis,使用 yAxisIndex 来指定它自己使用的是哪个 yAxis:
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) const option = { legend: {}, tooltip: {}, xAxis: { type: 'category' }, yAxis: [{ min: 0, max: 100 }, { min: 0, max: 100 }], dataset: { source: [ ['product', '2012', '2013', '2014', '2015'], ['Matcha Latte', 41.1, 30.4, 65.1, 53.3], ['Milk Tea', 86.5, 92.1, 85.7, 83.1] ] }, series: [ { type: 'bar', seriesLayoutBy: 'row', yAxisIndex: 0 }, { type: 'line', seriesLayoutBy: 'row', yAxisIndex: 1 } ] } chart.setOption(option) </script> </body> </html>
:::
再来看下图,一个 ECharts 实例中,有多个 grid,每个 grid 分别有 xAxis、yAxis,他们使用 xAxisIndex、yAxisIndex、gridIndex 来指定引用关系:
::: details
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script> <style> #chart { width: 800px; height: 400px; } </style> </head> <body> <div id="chart"></div> <script> const chartDom = document.getElementById('chart') const chart = echarts.init(chartDom) const option = { legend: {}, tooltip: {}, xAxis: [{ type: 'category', gridIndex: 0 }, { type: 'category', gridIndex: 1 }], yAxis: [{ gridIndex: 0 }, { gridIndex: 1 }], dataset: { source: [ ['product', '2012', '2013', '2014', '2015'], ['Matcha Latte', 41.1, 30.4, 65.1, 53.3], ['Milk Tea', 86.5, 92.1, 85.7, 83.1], ['Cheese Cocoa', 24.1, 67.2, 79.5, 86.4] ] }, grid: [{ bottom: '55%' }, { top: '55%' }], series: [ // 这几个系列会在第一个直角坐标系中,每个系列对应到 dataset 的每一行。 { type: 'bar', seriesLayoutBy: 'row' }, { type: 'bar', seriesLayoutBy: 'row' }, { type: 'bar', seriesLayoutBy: 'row' }, // 这几个系列会在第二个直角坐标系中,每个系列对应到 dataset 的每一列。 { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 }, { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 }, { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 }, { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 } ] } chart.setOption(option) </script> </body> </html>
:::
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。