This library helps generating [SVG paths] 1 with a high level API. These paths can be then used together with a template engine such as [Mustache] 2 or [Handlebars] 3 to display SVG graphics in the browser. If instead of a static template engine, you use a data binding library, such as [Ractive.js] 4, [Angular] 5 or [Facebook React] 6, you get animated graphics for free.
Paths.js offers three APIs, of increasing abstraction. The lowest level is a chainable API to generate an arbitrary SVG path. On top of this, paths for simple geometric shapes such as polygons or circle sectors are defined. At the highest level, there is an API to generate some simple graphs (pie, line chart, radar...) for a collection of data, assembling the simple shapes.
- What does it look like?
- Is it for me?
- Installation and usage
- Low level API
- Mid level API (shapes)
- High level API (graphs)
- Miscellaneous
- Browser support
Well, it depends on how you style the graphics, because the actual drawing of the SVG is left to you. Anyway, [here] 7 is a demo application; a live version can be seen [here] 8.
It depends. If what you need are some ready-made widgets and charts, probably not. In this case, libraries such as [Flotcharts] 9 or [Dimple] 10 may be a better fit. On the other hand, if you want to create your own charts, possibly with custom styling, interactions or animations, it may be a better idea to use a data-binding library and generate the SVG elements yourself. In this case, you will probably need to write some SVG paths, and Paths.js is designed to do exactly this. Another situation where you may want to deal directly with SVG elements is the case where you need to generate the graphics server side with Node.js. In this case you can couple Paths.js with any templating library of your choice, since Paths.js does not make use of any browser API (or any API outside the core ES5, actually).
The philosophy behind Paths.js is well explained in this blog post by Marcello La Rocca. Also, check the slides from my talk at MilanoJS user group, together with the examples.
Another presentation by Marcello goes in detail over the state of graphics in the browser, covering various approaches, including Paths.js.
Paths.js is distributed with [bower] 11, so you can install it with
bower install paths-js
It is comprised of various AMD modules. If you use [RequireJS] 12, you can use a configuration such as
require.config({
'paths': 'components/paths-js/dist/amd'
});
(the actual path will depend on your bower configuration). Then import the various modules like
var Pie = require('paths/pie');
If you want to use it on the server, just do
npm install paths-js
to install it and then
var Pie = require('paths-js/pie');
If you want to use Paths.js in the browser, but you do not want to use AMD modules, there is the possibility to include it in the global object. To do this, just include the file dist/global/paths.js
in a page, and then access the various APIs globally as paths.Pie
, paths.Polygon
and so on. Paths.js at version 0.2.1 weighs only 10.8kB minified and 3.9kB minified and gzipped, but of course if you choose the AMD version, you get to include exactly the modules you need.
At the heart of the library there is a very simple API to compose SVG paths by method chaining. At this level, we do not try to abstract away the specification of SVG paths, and the parameters mimic exactly the ones in the specification. An empty path object is created with the function Path()
and complex path objects can be obtained from the empty one by chaining path methods. Thus, one can produce a path like:
var Path = require('paths/path');
var path = Path()
.moveto(10, 20)
.lineto(30, 50)
.lineto(25, 28)
.qcurveto(27, 30, 32, 27)
.closepath();
Other than methods to compose other paths, path objects have the methods print
and points
. The print
method will give the textual representation of the path, that can be used inside an SVG figure like:
<!-- inside a template -->
<svg width=300 height=300>
<path d="{{ path.print() }}" fill="blue" />
</svg>
The points
method returns the array of points through which the path passes. This case be useful, for instance, to place labels near the endpoints.
The instructions
method returns the array of instructions to build the path. This is used to access programmatically the single instructions and to join paths.
The connect
method, applied to another path, adds the second path to the first one: if the end point of the first path is different from the start point of the second path, they are joined by a straight line.
All methods except print
and points
produce a new path (paths are immutable). These methods mimic the SVG path specification and are moveto
, lineto
, hlineto
, vlineto
, curveto
, qcurveto
, smoothcurveto
, smoothqcurveto
and closepath
.
It is also possible to use a more verbose API, where the parameters to the path methods are named. To do so, just pass an object to the path methods. The names of the parameters are the same as in the SVG specification. Hence:
Path()
.moveto(2, 10)
.lineto(3, 5)
.hlineto(4)
.vlineto(3)
.curveto(1, 1, 2, 5, 3, 1)
.smoothcurveto(2, 5, 2, 6)
.qcurveto(0, 1, 2, 3)
.smoothqcurveto(6, -3)
.arc(3, 3, 2, 0, 1, 6, -3)
.closepath()
is equivalent to:
Path()
.moveto({x: 2, y: 10})
.lineto({x: 3, y: 5})
.hlineto({x: 4})
.vlineto({y: 3})
.curveto({x1: 1, y1: 1, x2: 2, y2: 5, x: 3, y:1})
.smoothcurveto({x2: 2, y2: 5, x: 2, y: 6})
.qcurveto({x1: 0, y1: 1, x: 2, y: 3})
.smoothqcurveto({x: 6, y: -3})
.arc({rx: 3, ry: 3, xrot: 2, large_arc_flag: 0, sweep_flag: 1, x: 6, y: -3})
.closepath()
The verbose API can be freely mixed with the shorter one, so for instance:
Path()
.moveto(2, 10)
.lineto(3, 5)
.curveto({x1: 1, y1: 1, x2: 2, y2: 5, x: 3, y:1})
.smoothcurveto(2, 5, 2, 6)
.qcurveto({x1: 0, y1: 1, x: 2, y: 3})
is perfectly valid.
At a higher level of abstraction, we have some simple shapes. A module for a shape defines a function that takes as input some geometric data and returns a shape object. Shape objects have the two properties path
and centroid
. The first one contains a Path
, in the sense of the previous paragraph, while the second one is a point that is somehow central to the figure - for instance, it can be used to place a label. Thus a shape object has the structure:
{
path: <path object>
centroid: [<x>, <y>]
}
The first shape is paths.polygon
, and it can be used like:
var Polygon = require('paths/polygon');
var points = [[1, 3], [2, 5], [3, 4], [2, 0]];
var polygon = Polygon({
points: points,
closed: true
});
As shown in the example, it expects as input an object with the property points
, which is an array of points. The optional property closed
defined whether the polygon is closed (false by default).
A special case of the above is a polygon whose points are placed on half-lines starting from a fixed center and forming constant angles between each other; we call these kinds of polygons semi-regular. In the even more special case where all points have the same distance from the center, the polygon is regular.
A semi-regular polygon is defined by its center and the distance of each of its points from the center, like:
var SemiRegularPolygon = require('paths/semi-regular-polygon');
var polygon = SemiRegularPolygon({
center: [0, 0],
radii: [2, 3, 5, 7, 9, 12]
});
var regularPolygon = SemiRegularPolygon({
center: [1, 2],
radii: [3, 3, 3, 3, 3]
});
In the above example, polygon
is semi-regular and centered at the origin, while regularPolygon
is a regular pentagon centered at [1, 2]
.
Another special case of Polygon
is a rectangle having sides parallel to the axes. It can be generated with:
var Rectangle = require('paths/rectangle');
var rectangle = Rectangle({
top: 10,
bottom: 3,
left: -2,
right: 5
});
The SVG spec includes <rect>
elements, so usually there is no need to use a <path>
element to draw a rectangle, but it can be useful from time to time, in particular when generating bar charts.
Similar to paths.polygon
, the module paths.bezier
defines a curve that passes through a given list of vertices, but does so with a line that interpolates smoothly between the data points. Unlike polygons, curves produced in this way are always open.
An example is:
var Bezier = require('paths/bezier');
var points = [[1, 3], [2, 5], [3, 4], [4, 0]];
var curve = Bezier({
points: points,
tension: 0.4
});
The parameter tension
is optional and defaults to 0.3
; curves with smaller tension will look more pointy at the vertices.
A circular sector can be defined with paths.sector
:
var Sector = require('paths/sector');
var sector = Sector({
center: [10, 20],
r: 5,
R: 15,
start: 0,
end: Math.PI / 2
});
The Sector
function takes as input an object having the following properties. center
contains the coordinates of the center of the circles defining the sector; r
and R
are the internal and external radii; start
and end
are the start and end angle in radians. One can put r = 0
to get a degenerate sector.
A connector is an S-shaped path between two given points and lives in paths.connector
:
var Connector = require('paths/connector');
var connector = Connector({
start: [1, 12],
end: [6, 3]
});
The Connector
function takes as input an object having the start
and end
properties, both of which are points in the plane.
Based on the shapes above, we can construct more complex graphs. At this level, the API assume one has a collection of data that has to be shown on a graph, and take care of normalizing the data, so that for instance if you display multiple line graphs on the same chart, the scales are normalized.
All graph objects - that is, objects returned by some graph functions - have the field curves
that contains an array, and possibly more fields, depending on the graph. Each element of curves
has the properties item
, which is a reference to the corresponding data item, index
, and one or more field containing shape objects, for instance sector
in the case of the pie graph, or line
and area
for the line charts. Thus, a graph object has the shape:
{
curves: [
{
item: <datum>,
index: <index>,
<label>: <shape object>,
...
},
...
],
...
}
All of the following graph APIs accept a parameter named compute
which is a hash table of functions that should be evaluated for each curve to be drawn. A typical use would be to compute a color based on the index or the data item, like:
{
compute: {
color: function(i, item) {
...
}
}
}
All curves in the curves
array will contain the corresponding properties in addition to index
and item
; for instance in the example above each curve will have the color
property. Each function in the compute
hash receives two parameters index
and item
; where it makes sense it also receives a third parameter group
containing a group index (for items that are clustered).
This feature is useful if the resulting graph will be rendered with a somewhat static template engine, such as Mustache, that needs to have all fields precomputed. More flexible template engines, such as the one in Ractive, allow custom expressions to be evaluated in templates, so there will be generally no need for the compute
parameter.
The Pie
graph can be used as follows:
var Pie = require('paths/pie');
var 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: [20, 15],
r: 30,
R: 50
});
Parameters:
center
,r
,R
: have the same geometric meaning as in theSector
functiondata
: contains an array with the data to plot. The precise form of the data is not important, because the actual value of the data will be extracted by theaccessor
function.accessor
: a function that is applied to each datum indata
to extract a numeric valuecompute
(optional): see the introduction.
The object returned by the Pie
function contains the curves
array, on which one can iterate to draw the sectors. Each member of this array has the properties sector
, index
and item
, the latter containing the actual datum associated to the sector.
The Bar
graph can be used as follows:
var Bar = require('paths/bar');
var bar = Bar({
data: [
[
{ name: 'Italy', population: 59859996 },
{ name: 'Spain', population: 46704314 }
{ name: 'France', population: 65806000 },
{ name: 'Romania', population: 20121641 },
{ name: 'Greece', population: 10815197 }
],
[
{ name: 'Zambia', population: 14580290 },
{ name: 'Cameroon', population: 20386799 }
{ name: 'Nigeria', population: 173615000 },
{ name: 'Ethiopia', population: 86613986 },
{ name: 'Ghana', population: 24658823 }
]
],
accessor: function(x) { return x.population; },
compute: {
color: function(i) { return somePalette[i]; }
},
width: 500,
height: 400,
gutter: 10
});
Parameters:
width
,height
: have the obvious geometric meaningdata
: contains an array of arrays with the data to plot. The precise form of the data is not important, because the actual value of the data will be extracted by theaccessor
function. Each array will be represented by a series of bars with the same index.accessor
: a function that is applied to each datum inside each item indata
to extract a numeric valuegutter
(optional): the space to leave between each group of barscompute
(optional): see the introduction.
The bar chart allows multiple histograms to be drawn side by side. If you just have one series to be plotted, put it inside an array of length one anyway, like this:
Bar({
data: [[2, 5, 3, 9, 7]],
...
});
The object returned by the Bar
function contains the curves
array, on which one can iterate to draw the rectangles. Each member of this array has the properties line
, index
and item
, the latter containing the actual datum associated to the rectangle.
The Stock
graph is used to represent one or more line charts. It can be used as follows:
var Stock = require('paths/stock');
var data = [
[
{ year: 2012, month: 1, value: 13 },
{ year: 2012, month: 2, value: 12 },
{ year: 2012, month: 3, value: 15 }
],
[
{ year: 2012, month: 1, value: 21 },
{ year: 2012, month: 2, value: 22 },
{ year: 2012, month: 3, value: 22 }
]
];
function date(data) {
var d = new Date();
d.setYear(data.year);
d.setMonth(data.month - 1);
return d.getTime();
}
var stock = Stock({
data: data,
xaccessor: date,
yaccessor: function(d) { return d.value; },
width: 300,
height: 200,
compute: {
color: function(i) { return somePalette[i]; }
},
closed: true
});
Parameters:
width
andheight
: have the obvious geometric meaning; data will be rescaled to fit into a rectangle of these dimensionsdata
: contains the actual data to plot. It should be an array of arrays, each internal array representing a time series to be plotted. The actual format of the data in the time series is not important; the actual abscissa and ordinate of the point are extracted by thexaccessor
andyaccessor
function.xaccessor
,yaccessor
: two functions that extract from each datum its x and y coordinates. They default tofunction(d) { return d[0] }
andfunction(d) { return d[1] }
respectively, so ifdata
is passed as an array of arrays of arrays of 2 elements, the accessor functions are optional.closed
(optional, defaultfalse
): a boolean used to decide how to construct the paths for the area plots. Ifclosed
is set to true, these will be stretched to include part of the x axis, even if the data are not around 0. Use this if you want to be sure that the area paths touch the horizontal axiscompute
(optional): see the introduction.
The Stock
function will then return an object with the properties curves
, xscale
and yscale
. Under curves
it contains an array of objects, each having the properties line
, area
, item
and index
. line
and area
are two polygon objects, as in the previous paragraph; the first one holds the polygon for the line chart, while the second one is a closed polygon that can be used to draw the area fill. Under item
one finds the original element in the data.
Finally, xscale
and yscale
are the scales used to represent the data on the given width and height. They can be used to find the coordinates of the axis and draw them.
The smooth line graph is used to represent one or more line charts; unlike Stock
it interpolates between the data points with smooth Bézier curves. The API for paths.smooth-line
is identical to paths.stock
, so the two can be used interchangeably.
The radar graph can be used as follows:
var Radar = require('paths/radar');
var data = [
{ hp: 45, attack: 49, defense: 49, sp_attack: 65, sp_defense: 65, speed: 45 },
{ hp: 60, attack: 62, defense: 63, sp_attack: 80, sp_defense: 80, speed: 60 },
{ hp: 80, attack: 82, defense: 83, sp_attack: 100, sp_defense: 100, speed: 80 },
{ hp: 45, attack: 25, defense: 50, sp_attack: 25, sp_defense: 25, speed: 35 }
]
var radar = Radar({
data: data,
accessor: {
attack: function(x) { return x.attack; },
defense: function(x) { return x.defense; },
speed: function(x) { return x.speed; }
},
compute: {
color: function(i) { return somePalette[i]; }
},
max: 100,
center: [20, 15],
r: 30,
rings: 5
});
Parameters:
data
: contains an array of data to be plotted.accessor
: an object that describes how to extract the various features from the data. The keys of this object correspond to the axes that are shown in the radar chart, and associated to each key is a function that maps a datum to its value along this axis.accessor
is optional in the case where each datum is itself an object with numeric properties. For instance, if in the example aboveaccessor
was left out, we would obtain a radar graph of hexagons.max
: represents the ideal maximum of each feature.max
is optional; if it is left out, it is computed as the actual maximum of each feature, but one may want to override the computed value, for instance for constancy of scale during an animation.r
andcenter
: the radius and the center of the figure, respectively. So, the whole figure is scaled in such a way that a feature with valuemax
will be sent to a distancer
from thecenter
.rings
(optional, default3
): the number of polygonal rings that shall appear in the chart.compute
(optional): see the introduction.
The return value from Radar
is an object with the properties curves
and rings
. curves
is an array of objects, each one having the properties polygon
, item
and index
, where polygon
contains the actual path object. rings
is an array of path objects, representing concentric regular polygons of increasing radius.
The tree graph can be used as follows:
var Tree = require('paths/tree');
var data = {
name: 1,
descendants: [
{
name: 2,
descendants: [
{
name: 4,
descendants: [{
name: 6,
descendants: [{ name: 7 }]
}]
},
{ name: 5 }
]
},
{
name: 3,
descendants: [{ name: 8 }, { name: 9 }]
}
]
};
var tree = Tree({
data: data,
children: function(x) { return x.descendants; },
compute: {
color: function(i) { return somePalette[i]; }
},
width: 400,
height: 300
});
Parameters:
data
: contains a tree-like structure with the data to be plotted.width
andheight
: the dimensions of the graphchildren
(optional): a function that returns the list of children of the given node. Defaults tofunction(x) { return x.children; }
compute
(optional): see the introduction.
The return value from Tree
is an object with the properties curves
and nodes
. curves
is an array of objects, each one having the properties connector
, item
and index
, where connector
contains the actual path object. nodes
is an array of objects, each one having the properties point
and item
, representing the nodes of the tree.
The Waterfall
graph is a nice way to represent some values on a bar chart, together with other values that add up to their difference. A typical use would be breaking incomes or expenses into pieces. Since a picture is worth a thousand word, make sure to have a look at the demo. It can be used as follows:
var Waterfall = require('paths/waterfall');
var waterfall = Waterfall({
data: [
{
name: 'Gross income',
value: 30,
absolute: true
}
{
name: 'Transport',
value: -6
}
{
name: 'Distribution',
value: -3
}
{
name: 'Detail income'
absolute: true
}
{
name: 'Taxes',
value: -8
}
{
name: 'Net income'
absolute: true
}
],
compute: {
color: function(i, item) {
if (item.absolute) return 'green';
else return 'red';
}
},
width: 500,
height: 400,
gutter: 10
});
Parameters:
width
,height
: have the obvious geometric meaningdata
: contains an array with the data to plot. The precise form of the data is not important, because the actual value of the data will be extracted by theaccessor
function.accessor
: a function that is applied to each datum inside each item indata
to extract its value. It should return an object with either theabsolute
orvalue
property, or both.value
represents the height of the current bar.absolute
should betrue
if the bar should stand on its own, rather than appearing as the difference between consecutive bars.max
,min
(optional): maximum and minimum values represented on the chart. They are computed if they are not given explicitly, but setting them explicitly can be useful to draw more than one chart on the same scale.gutter
(optional): the space to leave between each barcompute
(optional): see the introduction.
The first item in the waterfall chart should have the value
property and absolute
set to true
. The subsequent bars will usually have either value
or absolute
but not both. If they have value
, this is considered relative to the previous bar. If they have absolute
set to true
, the value will be computed by summing all relative values up to that point.
For instance, the height of Detail income
in the example is 21 = 30 - 6 - 3
, while the height of Net income
is 13 = 21 - 8
.
The object returned by the Waterfall
function contains the curves
array, on which one can iterate to draw the rectangles. Each member of this array has the properties line
, index
, value
and item
, the latter containing the actual datum associated to the rectangle. value
instead contains the height computed for this rectangle: it coincides with item.value
whenever this is present, and otherwise it is equal to the cumulative value computed. For instance, value
would be 21
for the Detail Income
rectangle in the example above.
Force-directed graphs are still an experimental component of Paths.js. They work as advertised, but the performance is currently very bad. In the next iterations we will improve and optimize the layout algorithm, while leaving the API unchanged. In this way, graphs drawn today will still work, just with a smoother experience and possibly a nicer layout of nodes.
A graph is laid out with a physical simulation, where nodes repel each other, but nodes connected via links are attracted. The use of graphs is more complicated than other Paths.js APIs since we must be able to support server-side rendering, as well as client-side animations and drag and drop interaction.
The usage is as follows:
var Graph = require('paths/graph');
var graph = Graph({
data: {
nodes:[
{id:"pippo"},
{id:"pluto"},
{id:"paperino"}
{id:"qui"},
{id:"quo"},
{id:"qua"}
{id:"nonna papera"},
{id:"ciccio"}
],
links:[
{start:"pippo", end:"quo", weight:10},
{start:"pippo", end:"qua", weight:30},
{start:"pluto", end:"nonna papera", weight:10},
{start:"pluto", end:"qui", weight:10},
{start:"pluto", end:"quo", weight:10},
{start:"paperino", end:"ciccio", weight:100},
{start:"qui", end:"ciccio", weight: 20},
{start:"quo", end:"ciccio", weight: 10},
{start:"qua", end:"nonna papera", weight: 30}
]
},
compute: {
color: function(i) { return somePalette[i]; }
},
node_accessor: function (x) { return x.id; },
width: 500,
height: 400,
attraction: 1.1,
repulsion: 1.3,
threshold: 0.6
});
Parameters:
width
,height
: have the obvious geometric meaningdata
: contains an object with nodes and links. The precise form of the data is not important, because the actual value of the data will be extracted by thenode_accessor
andlink_accessor
functions.node_accessor
(optional, default identity): a function that is applied to each datum inside each item indata.nodes
to extract its id.link_accessor
(optional, default identity): a function that is applied to each datum inside each item indata.links
.attraction
(optional, default 1): a physical parameter: increasing it will make the links strongerrepulsion
(optional, default 1): a physical parameter: increasing it will make nodes repel each other more stronglythreshold
(optional, default 0.5): a parameter between0
and1
: higher values will lead to more accurate layouts but slower renderingcompute
(optional): see the introduction
nodes
is a list of objects; each element in a list is an object from which we can extract an id with the node_accessor
function. Ids should be unique to avoid wrong associations.
links
is a list of objects. Applying the link_accessor
function, we should extract something which contains a start
and end
properties (ids of nodes) and a weight
,
The object returned by the Graph
function contains two arrays curves
and nodes
. One can iterate on curves
to draw the links and on nodes
to draw the points. Each member of curves
has the properties link
, index
, item
, the latter containing the actual datum associated to the link. Each member of nodes
has the properties point
and item
. You can add more properties by passing them within the compute
object.
Finally, there is a significant difference between Graph
and the other Paths.js APIs. A force-directed graph is meant to be animated, where the layout is computed in steps by a physical simulation. The object we have described above is static, but it has a method tick
. The result of tick
is the next step of the graph. An excerpt of its use in the demo application looks like:
var moving = true;
function step() {
ractive.set({graph: graph.tick()});
if (moving) requestAnimationFrame(step);
}
requestAnimationFrame(step);
setTimeout(function() { moving = false; }, 10000);
It makes use of Ractive.js, but it should be easy to understand: on each animation frame we update the graph using graph.tick
and then render it.
If you make use of Graph
on the server side, you will need to perform a number of ticks before returning the graph to display.
Graph objects also have to other methods, constrain
and unconstrain
. One can use graph.constrain(id, coordinates)
to guarantee that the node represented by id
will be stuck at coordinates
regardless of the layout algorithm (starting from next tick), and graph.unconstrain(id)
to release the node. This can be used to enable drag and drop of the nodes, as in this example:
var svgX, svgY = null; // coordinates of the SVG frame
var following = null;
ractive.on('constrain', function(event) { // runs on mousedown
moving = true;
target = event.original.target;
svgX = event.original.clientX - target.cx.baseVal.value;
svgY = event.original.clientY - target.cy.baseVal.value;
following = event.index.num // node id
requestAnimationFrame(step); // reenable the animation if it was stopped
}
ractive.on('move', function(event) { // runs on mousemove
if (!following) return null;
if (event.original.button != 0) return null;
coordinates = [event.original.clientX - svgX, event.original.clientY - svgY];
graph.constrain(following, coordinates);
}
ractive.on('unconstrain', function(event) { // runs on mouseup
graph.unconstrain(following);
following = null;
setTimeout(function() { moving = false; }, 10000);
}
Sankey diagrams are a specific type of flow diagram, in which the arrows are proportional to the flow quantity. They are classically used to visualize energy accounts or material flow on a regional or national level, but they can represent any kind of quantitative flow, from source (to the left) to target (to the right). They are also closely related to "Alluvial diagrams". It can be used as follows:
var Sankey = require('paths/sankey');
var sankey = Sankey({
data: {
nodes:[
[{id:"pippo"},{id:"pluto"},{id:"paperino"}],
[{id:"qui"},{id:"quo"},{id:"qua"}],
[{id:"nonna papera"},{id:"ciccio"}]
],
links:[
{start:"pippo", end:"quo", weight:10},
{start:"pippo", end:"qua", weight:30},
{start:"pluto", end:"nonna papera", weight:10},
{start:"pluto", end:"qui", weight:10},
{start:"pluto", end:"quo", weight:10},
{start:"paperino", end:"ciccio", weight:100},
{start:"qui", end:"ciccio", weight: 20},
{start:"quo", end:"ciccio", weight: 10},
{start:"qua", end:"nonna papera", weight: 30}
]
},
compute: {
color: function(i) { return somePalette[i]; }
},
node_accessor: function (x) { return x.id; },
width: 500,
height: 400,
gutter: 10,
rect_width: 10
});
Parameters:
width
,height
: have the obvious geometric meaningdata
: contains an object with nodes and links. The precise form of the data is not important, because the actual value of the data will be extracted by thenode_accessor
andlink_accessor
functions.node_accessor
(optional, default identity): a function that is applied to each datum inside each item indata.nodes
to extract its id.link_accessor
(optional, default identity): a function that is applied to each datum inside each item indata.links
.gutter
(optional, default 10): the space to leave between each barrect_width
(optional, default 10): the width of each barcompute
(optional): see the introduction. Each function here has three parameters:index
,item
andgroup
, where the third one represents the outer index in thenodes
array.
nodes
is a list of lists of objects. Each list represent a level of the diagram; each element in a list is an object from which we can extract an id with the node_accessor
function. Ids should be unique to avoid wrong associations.
links
is a list of objects. Applying the link_accessor
function, we should extract something which contains a start
and end
properties (ids of nodes) and a weight
, which represents how much flow is going from start
to end
; start
should be on the left of end
and you should avoid circles (which is automatic if you respect the previous rule!). You don't need to have start
and end
in two consecutive levels.
The object returned by the Sankey
function contains the curvedRectangles
array, on which one can iterate to draw the flows, and the rectangles
array, on which one can iterate to draw the rectangles. Each member of this arrays has the properties curve
, index
, item
, the latter containing the actual datum associated to the node/link. You can add more properties by passing them within the compute
object.
Other than the modules mentioned above, Paths.js has the linear
and ops
modules. The linear
module contains a function that can be used to generate linear scale, that is, functions that interpolate linearly a source interval on a target one (affine functions of one variable). An example of use to map the interval [0, 3]
on the interval [10, 40]
would be
var Linear = require('paths/linear');
var scale = Linear([0, 3], [10, 40]);
var x = scale(2); // yields 30
The ops
module contains various utility functions that are used internally. It is not meant for external use, hence it is not documented, but curious folks can have a look at its tests.
Paths.js works in any environment that supports a modern version of Javascript, namely ES5. This includes any version of Node.js and all recent browsers. If you need support for older browsers, you can include an [ES5 polyfill] 13.
On the other hand, not every browser will be able to display the SVG graphics that you will generate. Usually, recent desktop browsers are ok, but mobile browser are slow in adopting the SVG specification. You can refer to [caniuse] 14 for more detailed information. Moreover, the [canvg] 15 project allows to draw SVG paths on a <canvas>
element, and it seems that canvas [will be able] 16 to support SVG paths natively. Of course, this solutions limits the possibilities offered by data binding libraries for interaction, but they could be used as a fallback on less recent browsers.