在最近的一篇文章中,我们讨论了浏览器中数据可视化的最新状态,尤其是SVG库。 在那里,我们专注于Snap.svg ,但我们引入了Paths.js作为可行的替代方案-与模板引擎或数据绑定库一起使用。 公平地说,正如我们将在以下各节中展示的那样,Paths的功能还很多,并带有实际用例来指导您了解其逻辑。
介绍
Paths的主要目的是通过直观的界面帮助前端开发人员生成具有更好性能的SVG路径。 顾名思义,尽管具有诸如rect
或circle
类的图元,但所有内容都可以简化为路径。 这种方法统一了不同的图表,提供了一个一致的界面,在该界面上,绘制命令始终返回准备绘制的路径列表。 通过使用数据绑定库(例如Ractive.js , Angular或React )替换静态模板引擎(例如Mustache或Handlebars),您甚至可以免费获得动画图形。
关于Paths.js最好的事情可能是它提供了三个增量API,并且抽象级别不断提高。 最低级别是可链接的API,可生成任意SVG路径。 在此之上,定义了简单几何形状(例如多边形或圆形扇区)的路径。 最高级别的API允许生成一些简单的图形,这些图形可以与数据集合一起提供。 (查看此演示以查看可用的原语。)
实际上,请注意:路径的最佳功能是您可以像使用Node.js在服务器端一样使用该库,因为它不直接依赖于任何库1 。 这样,您可以将图表结构和详细信息的生成移到服务器上。 除了加速应用程序之外,您还可以避免将原始数据完全发送到客户端,从而节省了延迟并减少了与客户端共享的信息量。
为什么选择路径?
使用Paths.js而不是D3或Snap.svg的最大优势在于,后者是必须的,而当Paths与模板引擎或(甚至更好)数据绑定框架配对时,它固有地支持声明式编程。
在像Ractive或React这样的框架中使用Paths又会带来另一个优势。 实际上,这些框架使用特定的优化来减少每次需要修改DOM时所需的重排和重绘次数。 他们保留DOM的“影子”副本,以某种“批处理模式”对其执行更新,并最终以最少的更改次数更新真实的DOM。
这些框架与众不同的另一个领域是事件处理。 默认情况下,它们使用事件委托,在将相同事件附加到一系列元素的情况下提高性能。 解决方案只是将这些事件处理程序附加到元素的某个通用容器上,但是当使用命令式方法(后果不堪设想,例如无响应的页面)时,忽略这种模式太容易了。
最后,Paths是轻量级的和模块化的:您可以只加载实际需要的组件,专注于图表,也可以加载SVG。 路径是那些专注于某些事物并试图对其进行优化的库之一。 通常,您可以将许多此类库组合在一起以执行复杂的任务。 另一方面,D3有许多额外的实用程序方法-如果您需要它们,这很好,因为您将所有需要的东西都放在一个地方,但是如果没有,就有点繁重。
使用路径
如前所述,您可以将Paths与Node.js或在浏览器上一起使用。 在后一种情况下,您可以将其作为AMD模块或独立库进行加载。
节点上的路径
如果要在服务器上使用它,请首先通过在控制台上键入以下命令来安装它(假设您已经正确安装了节点并且它在全局路径中):
npm install paths-js
安装后,您可以加载各个模块:
var Pie = require('paths-js/pie');
浏览器上的路径:AMD模块
Paths.js与Bower一起分发,您可以从命令行安装它:
bower install paths-js
或者,当然,只需从GitHub上的存储库手动下载它。
路径被构造为各种AMD模块,并且可以随AMD模块加载程序一起加载。 使用RequireJS (假设您已使用Bower安装了Paths),这是如何配置它的方法:
require.config({
'paths': 'components/paths-js/dist/amd'
});
实际路径将取决于Bower配置,或者对于手动下载,取决于文件夹的结构。 (请注意放置上面链接的amd
文件夹的位置。)
正确配置后,您可以轻松地需要各个模块:
var Pie = require('paths/pie');
浏览器上的路径:独立脚本
如果您不想使用AMD模块,则可以安全地将Paths作为独立脚本包括在内:所需文件为dist/global/paths.js
。 一旦将其包含在页面中, paths
对象将在全局范围内可用,因此各个模块将分别用作paths.Pie
, paths.Polygon
等。 除了冗长之外,您还失去了仅导入所需模块的能力–但是,如果需要其中许多模块,则其影响可忽略不计。
低级API
如上所述,最低级别的API目标是创建路径。 创建目标就像调用一个构造函数Path()
一样容易。 整个API是可链接的,因此您可以通过根据先前调用的结果调用方法来创建复杂的路径。 Path
对象提供了逐步扩展当前路径的方法。 保留对路径最后一点的引用,并且可以从该点添加直线或曲线,从而模仿路径的SVG语法。 您将需要的主要方法是:
-
moveto(x, y)
:将光标移动到传递的坐标。 -
lineto(x, y)
:绘制一条从路径末端到这些坐标的线。 -
curveto(x1, y1, x2, y2, x, y)
:以(x1,y1)作为控制点,在曲线的开始处绘制一个从当前点到(x,y)的三次贝塞尔曲线。 y2)作为曲线末端的控制点。 -
smoothcurveto(x2, y2, x, y)
:绘制从当前点到(x,y)的三次贝塞尔曲线,基于第二个和上一个命令(如果有)隐式计算第一个控制点。 -
arc('rx', 'ry', 'xrot', 'large_arc_flag', 'sweep_flag', 'x', 'y')
:从当前点到(x,y)绘制一个椭圆弧,控制椭圆半径和通过其他参数旋转。 -
closepath()
:关闭路径,将其变成多边形。
所有可用的方法也都支持“冗长”的API,以便可以将命名参数(以配置对象的形式)无缝地传递给每个参数。 例如,上面的moveto
方法可以称为Paths().moveto({x: 10, y: 3})
或Paths().moveto(10, 3)
。 参数名称遵循SVG规范。
有更多可用的方法,并且通常与SVG Paths命令一一对应。 例如, qcurveto(x1, y1, x, y)
和smoothqcurveto(x, y)
与二次曲线的curveto
smoothcurveto
类似。
通常,这与模板引擎一起使用会更好,但并非绝对必要。 您可以使用命令式的路径,如以下示例所示。 但是,这不是最佳实践:
<title>PathsJs test</title>
<style type="text/css">
.ocean {
fill: blue;
}
</style>
<svg width="640px" height="480px"><path id="testpath" class="ocean"></path></svg>
<script type="text/javascript" src="lib/paths.js"></script>
var Path = require('paths/path');
var path = Path()
.moveto(10, 20)
.lineto(30, 50)
.lineto(25, 28)
.qcurveto(27, 30, 32, 27)
.closepath();
document.getElementById("testpath").setAttribute("d", path.print());
Path
对象的print()
方法将构造的路径转换为相应的SVG数据字符串,就像它出现在path的d
(数据)属性中一样。 一旦获得该值,就可以使用CSS选择器和getElementById
/ getElementsBy*
方法在任何路径上手动设置适当的属性。
当然,使用模板引擎将极大地影响我们需要编写的样板代码的数量:
<svg width="640px" height="480px"><path d="{{ path.print() }}" fill="blue"></path></svg>
这样就无需为#testpath
手动设置d
属性,甚至无需为path元素分配ID。 这是使用“路径”创建SVG图纸的最佳实践样式。
中级API
尽管功能强大,但实际上您几乎不需要低级API。 原因是Paths提供了一个基于API的API,具有更高的抽象级别,可让您使用直观的语法直接创建多边形和形状。
在查看示例之前,了解此API的工作原理非常重要。 我们已经说明了该库产生的所有内容都是一条路径。 实际上,每个中级API方法都会返回一个带有两个字段的对象:
{
path: <path object="">
centroid: [<x>, <y>]
}
path
字段包含一个Path
对象,该对象与低级API兼容,因此可以扩展:可以照常使用其print()
方法检索描述路径的data属性的字符串。
相反, centroid
场与生成的路径没有正式关系,但是,它仍然非常有用:根据经验,它被计算为图形中心的点,可用于例如定位形状的标签。
为了理解两个抽象级别之间的区别,我们将使用Polygon
对象创建与上一节几乎相同的Polygon
:
<svg width="640px" height="480px"><path id="testpath" class="ocean"></path><path id="testpath2" class="ocean" transform="translate(100)"></path></svg>
var Polygon = require('paths/polygon');
var polygon2 = Polygon({
points: [[10, 20], [30, 50], [25, 28], [32, 27]],
closed: true
});
document.getElementById("testpath2").setAttribute("d", polygon2.path.print());
如果测试此代码,您会发现两个形状乍一看非常相似。 不同之处在于,第一个使用低级API构建,其一侧用二次曲线而不是线段构建。 实际上,低级API的优势在于允许您以相同的形状混合不同类型的线。
对于中级API,没有这样一种方法可以让您方便地混合它们。 但是不要担心,什么也不会丢失:正如我们所说的,您始终可以编辑Polygon(...)
返回的Path
:
<svg width="640px" height="480px"><path id="testpath" class="ocean"></path><path id="testpath2" class="ocean" transform="translate(100)"></path><path id="testpath3" class="ocean" transform="translate(50)"></path></svg>
var polygon3 = Polygon({
points: [[10, 20], [30, 50], [25, 28]],
closed: false
});
console.log(polygon3.path.print())
var polygon3Path = polygon3.path
.qcurveto(27, 30, 32, 27)
.closepath();
document.getElementById("testpath3").setAttribute("d", polygon3Path.print());
可用于第二级接口的对象的完整列表为:
- 多边形 :关闭和打开的多边形。
- 半正多边形 : 多边形的一种特殊情况:允许创建三角形,正方形,五边形等(正多边形及其不规则变体)。 它是相对于中心定义的,并且从中心到点的线段之间的角度都相同,而这些点的距离可以是恒定的(规则多边形),也可以是变化的(不规则多边形)。
- 矩形 :另一种特殊的多边形,尽管这次更直观。
- 贝塞尔曲线:绘制一条平滑的贝塞尔曲线,穿过一系列顶点。 返回的路径始终是开放的。
- 部门 :循环部门。
- 连接器 :定义为两个给定点之间的S形路径。 (了解它的最好方法是尝试一下。)
几个示例可以更好地显示半正多边形:
三角形(等边)
var SemiRegularPolygon = require('paths/semi-regular-polygon');
var triangle = SemiRegularPolygon({
center: [50, 50],
radii: [20, 20, 20]
});
document.getElementById("triangle").setAttribute("d", triangle.path.print());
三角形(等腰)
var triangleIrregular = SemiRegularPolygon({
center: [50, 50],
radii: [20, 30, 30]
});
广场
var square = SemiRegularPolygon({
center: [50, 50],
radii: [20, 20, 20, 20]
});
五角大楼
var pentagon = SemiRegularPolygon({
center: [50, 50],
radii: [20, 20, 20, 20, 20]
});
五角大楼(不规则)
var pentagonIrregular = SemiRegularPolygon({
center: [50, 50],
radii: [25, 20, 40, 30, 20]
});
高级API
这是Paths提供的最高级别的API。 其方法的目的是允许创建从要可视化的数据集开始的完整图表。 一如既往,一切都转化为道路! 特别是,所有这些方法都会返回一个对象,其中包含一个curves
字段,一个具有为每个数据点创建的形状的数组。 curves
中的形状是具有一些相关字段的对象:
-
item
:对相应数据项的引用。 -
index
:数据数组中相应数据项的索引。 - 一个或多个包含形状对象的字段(例如
sector
用于饼图,line
和area
用于折线图)。
根据每个图表,返回的对象除curves
外可能还有其他字段。 但是每个图表方法都在输入中接受一个compute
参数。 该参数允许用户传入许多函数,以根据输入数据计算额外的字段。 (计算颜色将是一个典型示例。)
使用这些高级方法时,模板引擎几乎是必需的,因为它们为开发人员节省了大量样板代码。 它们还通过自动执行复杂图表的形状集上的迭代来简化图表的创建。
但是,与往常一样,并非严格需要它们。 让我们看一个没有它们怎么做的例子:
<svg id="chart-test" width="200px" height="200px"></svg>
var somePalette = ['blue', 'green', 'red', 'yellow', 'orange'],
Pie = require('paths/pie'),
pie = Pie({
data: [
{ name: 'Italy', population: 59859996 },
{ name: 'Mexico', population: 118395054 },
{ name: 'France', population: 65806000 },
{ name: 'Argentina', population: 40117096 },
{ name: 'Japan', population: 127290000 }
],
accessor: function(x) { return x.population; },
compute: {
color: function(i) { return somePalette[i]; }
},
center: [50, 50],
r: 30,
R: 50
}),
chartSvg = document.getElementById("chart-test"),
chartFragment = document.createDocumentFragment(),
dx = parseInt(chartSvg.getAttribute('width'), 10) / 2,
dy = parseInt(chartSvg.getAttribute('height'), 10) / 2;
pie.curves.forEach(function (d, i){
var path = document.createElementNS('http://www.w3.org/2000/svg',"path");
path.setAttributeNS(null, 'd', d.sector.path.print());
path.setAttributeNS(null, 'style', 'fill:' + d.color);
var label = document.createElementNS('http://www.w3.org/2000/svg',"text");
label.textContent = d.item.name;
label.setAttributeNS(null, 'x', d.sector.centroid[0]);
label.setAttributeNS(null, 'y', d.sector.centroid[1]);
chartFragment.appendChild(path);
chartFragment.appendChild(label);
});
chartSvg.appendChild(chartFragment);
在上面的代码中,我们使用代码片段收集所有扇区,然后才将它们实际添加到页面中,因此仅触发一次重排,而不是每个扇区触发两次重熔(路径一次,标签一次)。 文档片段元素可以一次性插入,而如果我们使用svg:g
元素对它们进行分组,则每个节点将被单独插入。 (此外,结果SVG中可能会有多余的组。)文档片段的更大优势是,如果我们需要克隆整个图表并将其多次添加到页面中,则每个克隆操作都需要一个常量节点插入数,而不是线性数。
现在,让我们将之前的代码与使用Ractive创建与上述相同的图进行比较:
<div id="pie-chart"></div><script id="myChartTemplate" type="text/ractive">
<svg width=375 height=400>
{{# pie }}
{{# curves:num }}
<path on-click="expand" d="{{ sector.path.print() }}" fill="{{ color }}" ></path>
<text text-anchor="middle" x="d.sector.centroid[0]" y="d.sector.centroid[1]">{{ item.name }}</text>
</g>
{{/ curves }}
{{/ end of pie}}
</svg>
</script>
var Pie = require('paths/pie');
var ractive = new Ractive({
el: 'pie-chart',
template: '#myChartTemplate',
data: {
pie: Pie({
data: [
{ name: 'Italy', population: 59859996 },
{ name: 'Mexico', population: 118395054 },
{ name: 'France', population: 65806000 },
{ name: 'Argentina', population: 40117096 },
{ name: 'Japan', population: 127290000 }
],
accessor: function(x) { return x.population; },
compute: {
color: function(i) { return somePalette[i]; }
},
center: [50, 50],
r: 30,
R: 50
})
}
});
结果看起来更好,更干净,并且通过查看标记,图表的结构立即可见。
当前有9种不同类型的图表可用:
- 饼形图
- 条形图 :允许并排绘制多个直方图。
- 股票图表 :用折线图表示一个或多个时间序列。
- 平滑折线图 :类似于股票图表,但是它使用平滑的贝塞尔曲线在数据点之间插入线。
- 雷达图
- 树形图
- 瀑布图 :条形图,可细分数值。
- 力定向图 :图形形式的物理模拟,除非顶点通过边连接,否则顶点彼此排斥。
- Sankey图表 :流程图,其中箭头与流量成比例。
您可以查看路径[showcase](http://andreaferretti.github.io/paths-js-demo/),以查看这些图表的外观。 此处展示的所有示例都利用Ractive轻松创建了很棒的动画。
结论
在这一点上,您可能会问路径是否确实是您的正确选择。 当然,对此没有简单的答案。 这取决于。 如果您需要现成的窗口小部件和图表,可能不需要-使用Highcharts , Flotcharts或Dimple可能会更好。
但是,我们不能足够强调在数据可视化中采用声明式编程的优势。 如果您必须使用Paths,则可能不值得花精力学习新的库。
当您需要创建具有个性化样式或动画的自定义图表或响应用户交互的自定义行为时,路径会很有用。 但是Paths是真正的游戏改变者,您需要在服务器上生成图形。 路径使您非常容易在逻辑中生成标记并将其作为JSON或字符串发送到客户端。
最后,这里有一些链接可供进一步阅读:
- 本文中的代码示例的 CodePen。
- 我关于浏览器数据可视化的幻灯片演示: 使用Ractive.js的SVG动态图 。
- GitHub上的Paths-js主页 。
- 幻灯片播放了Andrea Ferretti关于在浏览器中进行可视化的功能方法的演讲 。
- Paths.js仅依赖于EcmaScript 5核心方法,这对于较旧的浏览器来说是一个问题。 该polyfill添加了必要的支持。 ↩
From: https://www.sitepoint.com/create-performant-template-based-charts-paths-js/