本文將視圖了解d3js提供的幫助我們創建矢量圖形的helper函數,比如下面的:
http://d3indepth.com/shapes/
lines

curves

pie chart segments

symbols

SVG
首先我們來認識一下SVG(scalable vector graphics).要知道上面例子中的圖形實際上都是由SVG的path元素構成的。每張圖都有不同的path元素來組成,這些path元素本身的d屬性來定義圖形的path.而path data由一系列的命令組成(比如: M0,80L100,100L200,30L300,50L400,40L500,80),如下面的代碼所示:
<path d="M12.061098497445768,-54.465861201009005A4,4,0,0,1,17.122890866582928,-57.50483986910224A60,
60,0,0,1,29.32181418613459,-52.34721781368898A4,4,0,0,1,30.66836903336433,-46.59883339919623L5.38995775477549,
-9.649482084709554A1.2313393364975447,1.2313393364975447,0,0,1,3.1668950166423597,-10.589377935221732Z"></path>
這些微代碼由瀏覽器來解讀,比如 ‘move to’ , ‘draw a line to’等,詳細可以參考 SVG specification
實際上,我們可以自己通過書寫代碼來創建這些path命令集,但是你會發現,寫出這些代碼雖然並不難,但是一定是很繁瑣的,d3js為了將我們從這些繁瑣的工作中解放出來,d3js的作者就發明了被成為"path generator"的路徑生成器。下面我們列出常見的路徑生成器:
| line | Generates path data for a multi-segment line (typically for line charts) |
| area | Generates path data for an area (typically for stacked line charts and streamgraphs) |
| stack | Generates stack data from multi-series data:實際上stack是一個layout |
| arc | Generates path data for an arc (typically for pie charts) |
| pie | Generates pie angle data from array of data:實際上pie是一個layout |
| symbol | Generates path data for symbols such as plus, star, diamond |
Line generator
我們給d3一個(x,y)坐標的數組,d3就能生成一串path data string
我們首先聲明一個line generator:
var lineGenerator = d3.line();
lineGenerator的任務就是接收一個坐標數組,輸出一個path data string直接可以作為path元素的d屬性值。
我們來定義一個坐標數組
var points = [ [0, 80], [100, 100], [200, 30], [300, 50], [400, 40], [500, 80] ];
接着我們傳入points參數來調用lineGenerator,
var pathData = lineGenerator(points); // pathData is "M0,80L100,100L200,30L300,50L400,40L500,80"
lineGenerator完成的工作就是創建了一個M(move to)和L(line to)命令的字符串,這樣我們就可以使用pathData來給d屬性賦值
d3.select('path')
.attr('d', pathData);
最終瀏覽器渲染出來下面的線圖:

對於line generator函數,我們可以有以下可以配置
.x()and.y()accessor functions,.defined()(to handle missing data),.curve(to specify how the points are interpolated) and.context()to render to a canvas element.
.x() and .y() accessor functions
By default each array element represents a co-ordinate defined by a 2-dimensional array (e.g. [0, 100]). However we can specify how the line generator interprets each array
默認情況下每一個數組元素都代表了一個2緯的數組,比如[0,100],然而我們也可以告訴line generator來如何解讀傳入的數據,而這就要使用對應的accessor functions了:.x()和.y().例如假設我們有以下對象的數組:
var data = [ {value: 10}, {value: 50}, {value: 30}, {value: 40}, {value: 20}, {value: 70}, {value: 50} ];
我們就可以這樣來定義accessor函數:
lineGenerator .x(function(d, i) { return xScale(i); }) .y(function(d) { return yScale(d.value); });
在這個例子中,我們使用數組的index來定義x,(注意我們同時使用了比例尺函數)
.defined()
我們可以使用defined函數來定義如果有部分數據不應該渲染的情況下如何操作,比如下面的數據:
var points = [ [0, 80], [100, 100], null, [300, 50], [400, 40], [500, 80] ];
我們告知line generator每一個坐標只有是non-null時才是有效的,通過下面的代碼:
lineGenerator .defined(function(d) { return d !== null; });
這樣當我們再次調用lineGenerator並渲染后就將得到一個有着斷續的line:

注意:如果沒有上面的.defined定義的話,將會產生一個錯誤,因為null無法獲取到對應的x,y坐標
.curve()
我們也可以定義這個path的points之間是如何插值的。比如,我們可以使用一個B-spline算來來插值:
var lineGenerator = d3.line() .curve(d3.curveCardinal);

雖然有相當多的不同類型的curve type在d3中可以使用,但是我們也可以把這些插值類型簡單分為兩類:一種是必須經過指定points的類型(比如:curveLinear,curveCardinal,curveCatmullRom,curveMonotone,curveNatural,curveStep),另一種是不必經過每一個points(比如curveBasics和curveBundle)
See the curve explorer for more information.
Rendering to canvas
By default the shape generators output SVG path data. However they can be configured to draw to a canvas element using the .context() function:
默認情況下shape generator輸出SVG path data.然而我們也可以通過.context()函數來指定使用canvas來繪圖:
var context = d3.select('canvas').node().getContext('2d'); lineGenerator.context(context); context.strokeStyle = '#999'; context.beginPath(); lineGenerator(points); context.stroke();
Radial line
The radial line generator is similar to the line generator but the points are transformed by angle (working clockwise from 12 o’clock) and radius, rather than x and y:
radial line generator和普通的line generator是類似的,唯一的不同是該generator對坐標的解讀是“極坐標系”(從12點開始順時針運行的)角度和半徑,而不是x,y坐標:
var radialLineGenerator = d3.radialLine(); var points = [ [0, 80], [Math.PI * 0.25, 80], [Math.PI * 0.5, 30], [Math.PI * 0.75, 80], [Math.PI, 80], [Math.PI * 1.25, 80], [Math.PI * 1.5, 80], [Math.PI * 1.75, 80], [Math.PI * 2, 80] ]; var pathData = radialLineGenerator(points);

Accessor functions .angle() and .radius() are also available:
類似於lineGenerator,對應的accessor function .angle()和.radius()如下:
radialLineGenerator .angle(function(d) { return d.a; }) .radius(function(d) { return d.r; }); var points = [ {a: 0, r: 80}, {a: Math.PI * 0.25, r: 80}, {a: Math.PI * 0.5, r: 30}, {a: Math.PI * 0.75, r: 80}, ... ]; var pathData = radialLineGenerator(points);
Area generator
The area generator outputs path data that defines an area between two lines. By default it generates the area between y=0 and a multi-segment line defined by an array of points:
區域生成器輸出一個通過兩條lines來定義的一個區域的path data來工作的。默認情況下,它在y=0的水平線和一個由一個點數組來定義的多段線線之間生成一個區域:
var areaGenerator = d3.area(); var points = [ [0, 80], [100, 100], [200, 30], [300, 50], [400, 40], [500, 80] ]; var pathData = areaGenerator(points);

我們可以通過.y0() accessor函數來定義這個base line:
areaGenerator.y0(150);
就變成了如下的圖形:(通常情況下我們使用圖形的height作為base line)

我們也可以給.y0() accessor函數一個函數來指明如何獲取y0的值,類似於.y1() accessor
areaGenerator .x(function(d) { return d.x; }) .y0(function(d) { return yScale(d.low); }) .y1(function(d) { return yScale(d.high); }); var points = [ {x: 0, low: 30, high: 80}, {x: 100, low: 80, high: 100}, {x: 200, low: 20, high: 30}, {x: 300, low: 20, high: 50}, {x: 400, low: 10, high: 40}, {x: 500, low: 50, high: 80} ];
典型地,.y0()定義了base line, .y1()定義了top line. 注意我們也使用了.x()定義了x坐標(base line和top line使用的是相同的x坐標哦!!)
和line generator一樣,我們可以指定在點之間是如何插值的(.curve()),以及如何處理missing data(.defined())以及如何在canvas而不是在svg中渲染(.context())
Radial area
radial area generator和area generator是類似的,唯一的不同是這些點我們是使用angle角度(從12點順時針開始的角度)和半徑radius來定義的,而不是x和y來定義的:
var radialAreaGenerator = d3.radialArea() .angle(function(d) { return d.angle; }) .innerRadius(function(d) { return d.r0; }) .outerRadius(function(d) { return d.r1; }); var points = [ {angle: 0, r0: 30, r1: 80}, {angle: Math.PI * 0.25, r0: 30, r1: 70}, {angle: Math.PI * 0.5, r0: 30, r1: 80}, {angle: Math.PI * 0.75, r0: 30, r1: 70}, {angle: Math.PI, r0: 30, r1: 80}, {angle: Math.PI * 1.25, r0: 30, r1: 70}, {angle: Math.PI * 1.5, r0: 30, r1: 80}, {angle: Math.PI * 1.75, r0: 30, r1: 70}, {angle: Math.PI * 2, r0: 30, r1: 80} ];
如下圖所示:

Stack generator
stack generator接收一個multi-series data而對每一個series來生成一個數組,而每個數組包含着各data point的lower和upper values
lower and upper values are computed so that each series is stacked on top of the previous series.
lower和upper values被用於計算位置這樣每個series都堆疊在前一個series上面
var data = [ {day: 'Mon', apricots: 120, blueberries: 180, cherries: 100}, {day: 'Tue', apricots: 60, blueberries: 185, cherries: 105}, {day: 'Wed', apricots: 100, blueberries: 215, cherries: 110}, {day: 'Thu', apricots: 80, blueberries: 230, cherries: 105}, {day: 'Fri', apricots: 120, blueberries: 240, cherries: 105} ]; var stack = d3.stack() .keys(['apricots', 'blueberries', 'cherries']); var stackedSeries = stack(data); // stackedSeries = [ // [ [0, 120], [0, 60], [0, 100], [0, 80], [0, 120] ], // Apricots // [ [120, 300], [60, 245], [100, 315], [80, 310], [120, 360] ], // Blueberries // [ [300, 400], [245, 350], [315, 425], [310, 415], [360, 465] ] // Cherries // ]
.keys()配置函數定義了在stack generation中哪些series被包含其中。
stack generator輸出的數據,你可以隨意使用,但是典型地,這個輸出數據被用於產生stacked bar charts:

or when used in conjunction with the area generator, stacked line charts:
或者,如果和area generator配合使用,形成stacked line charts:

.order()
stacked series出現的順序可以由.order()配置函數來
stack.order(d3.stackOrderInsideOut);
每個series匯總后通過選擇的順序來排列,可用的順序如下:
| stackOrderNone | (Default) Series in same order as specified in .keys() |
| stackOrderAscending | Smallest series at the bottom |
| stackOrderDescending | Largest series at the bottom |
| stackOrderInsideOut | Largest series in the middle |
| stackOrderReverse | Reverse of stackOrderNone |
.offset()
默認情況下stacked series的baseline為0.然而我們也可以配置stack generator的offset來達到不同的baseline效果。比如,我們可以規整stacked series以便他們都有着相同的高度:
stack.offset(d3.stackOffsetExpand);

可用的offset如下:
| stackOffsetNone | (Default) No offset |
| stackOffsetExpand | Sum of series is normalised (to a value of 1) |
| stackOffsetSilhouette | Center of stacks is at y=0 |
| stackOffsetWiggle | Wiggle of layers is minimised (typically used for streamgraphs) |
下面就是一個使用了stackOffsetWiggle的stacked chart:

Arc generator
Arc generators produce path data from angle and radius values. An arc generator is created using:
arc generator用於從angle和radius值來產生path data.下面創建一個arc
var arcGenerator = d3.arc();
隨后可以給該generator傳入一個包含着startAngle, endAngle,innerRadius,outerRadius屬性值的對象來生成path data:
var pathData = arcGenerator({ startAngle: 0, endAngle: 0.25 * Math.PI, innerRadius: 50, outerRadius: 100 }); // pathData is "M6.123233995736766e-15,-100A100,100,0,0,1,70.71067811865476,-70.710678 // 11865474L35.35533905932738,-35.35533905932737A50,50,0,0,0,3.061616997868383e-15,-50Z"
(注意:startAngle and endAngle是從12點開始順時針計量的角度數)
Configuration
We can configure innerRadius, outerRadius, startAngle, endAngle so that we don’t have to pass them in each time:
我們可以使用innerRadius,outerRadius,startAngle,endAngle函數來配置,而不用每次生成arc generator時傳入對象參數:
arcGenerator .innerRadius(20) .outerRadius(100); pathData = arcGenerator({ startAngle: 0, endAngle: 0.25 * Math.PI }); // pathData is "M6.123233995736766e-15,-100A100,100,0,0,1,70.71067811865476,-70.71067811 // 865474L14.142135623730951,-14.14213562373095A20,20,0,0,0,1.2246467991473533e-15,-20Z"
We can also configure corner radius (cornerRadius) and the padding between arc segments (padAngle and padRadius):
我們也可以配置corner radius(corner Radius)以及弧線段之間的padding值(padAngle,padRadius)
arcGenerator .padAngle(.02) .padRadius(100) .cornerRadius(4);

Arc padding takes two parameters padAngle and padRadius which when multiplied together define the distance between adjacent segments. Thus in the example above, the padding distance is 0.02 * 100 = 2. Note that the padding is calculated to maintain (where possible) parallel segment boundaries.
Arc padding有兩個參數:padAngle和padRadius他們合在一起定義了兩個相鄰弧段之間的距離。這樣在上面的例子中,padding distance就是0.02*100=2.注意padding計算時會盡可能地保持(如果可能的話)弧段之間的邊界保持平行。
你可能會問
你可能會問為什么不用一個簡單的padDistance參數來定一個這個padding distance,而使用兩個參數相乘這么復雜的參數來定義呢?之所以這樣,是因為pie generator無需關心半徑的大小。
Accessor functions
我們也可以為startAngle, endAngle, innerRadius,outerRadius來定義對應的accessor functions
arcGenerator .startAngle(function(d) { return d.startAngleOfMyArc; }) .endAngle(function(d) { return d.endAngleOfMyArc; }); arcGenerator({ startAngleOfMyArc: 0, endAngleOfMyArc: 0.25 * Math.PI });
Centroid
有時,計算弧線的中心點是很有用的,比如當我們要放置label時,往往希望放到中心點附件,d3給我們提供了一個.centroid()函數來實現:
arcGenerator.centroid({ startAngle: 0, endAngle: 0.25 * Math.PI }); // returns [22.96100594190539, -55.43277195067721]
下面的例子我們使用.centroid()來計算label的位置:
// Create an arc generator with configuration var arcGenerator = d3.arc() .innerRadius(20) .outerRadius(100); var arcData = [ {label: 'A', startAngle: 0, endAngle: 0.2}, {label: 'B', startAngle: 0.2, endAngle: 0.6}, {label: 'C', startAngle: 0.6, endAngle: 1.4}, {label: 'D', startAngle: 1.4, endAngle: 3}, {label: 'E', startAngle: 3, endAngle: 2* Math.PI} ]; // Create a path element and set its d attribute d3.select('g') .selectAll('path') .data(arcData) .enter() .append('path') .attr('d', arcGenerator); // Add labels, using .centroid() to position d3.select('g') .selectAll('text') .data(arcData) .enter() .append('text') .each(function(d) { var centroid = arcGenerator.centroid(d); d3.select(this) .attr('x', centroid[0]) .attr('y', centroid[1]) .attr('dy', '0.33em') .text(d.label); });

Pie generator
The pie generator goes hand in hand with the arc generator. Given an array of data, the pie generator will output an array of objects containing the original data augmented by start and end angles:
pie generator和弧線generator是類似的。給pie generator一個數據數組,pie generator就會輸出一個反映了原始start和end angles數據的對象數
var pieGenerator = d3.pie(); var data = [10, 40, 30, 20, 60, 80]; var arcData = pieGenerator(data); // arcData is an array of objects: [ // { // data: 10, // endAngle: 6.28..., // index: 5, // padAngle: 0, // startAngle: 6.02..., // value: 10 // }, // ... // ]
We can then use an arc generator to create the path strings:
隨后,我們可以使用一個arc generator來創建對應的path strings
var arcGenerator = d3.arc() .innerRadius(20) .outerRadius(100); d3.select('g') .selectAll('path') .data(arcData) .enter() .append('path') .attr('d', arcGenerator);
注意pieGenerator的輸出包含了startAngle和endAngle兩個屬性。這些正好是arcGenerator所需要的輸入!
再注意:實際上pieGenerator是一個layout了,而不是路徑生成器!
The pie generator has a number of configuration functions including .padAngle(), .startAngle(), .endAngle() and .sort(). .padAngle() specifies an angular padding (in radians) between neighbouring segments.
pie generator包含了一系列的配置函數,包括:.padAngle(),.startAngle(),.endAngle(),.sort(),.padAngle()定義了一個在相鄰段之間的angular padding
.startAngle() and .endAngle()配置pie chart的startAngle和endAngle. 這將允許創建半圓形的pie charts:
var pieGenerator = d3.pie() .startAngle(-0.5 * Math.PI) .endAngle(0.5 * Math.PI);

默認情況下段的起始和結束角度使得段之間以降序排列。然而我們可以更改這種排序方式,方法是使用.sort()函數:
var pieGenerator = d3.pie() .value(function(d) {return d.quantity;}) .sort(function(a, b) { return a.name.localeCompare(b.name); }); var fruits = [ {name: 'Apples', quantity: 20}, {name: 'Bananas', quantity: 40}, {name: 'Cherries', quantity: 50}, {name: 'Damsons', quantity: 10}, {name: 'Elderberries', quantity: 30}, ];
Symbols
The symbol generator produces path data for symbols commonly used in data visualisation:
symbol generator用於產生在可視化領域中常用的symbol的path data:
var symbolGenerator = d3.symbol() .type(d3.symbolStar) .size(80); var pathData = symbolGenerator();
隨后我們就可以使用這個pathData變量作為path元素的d屬性了:
d3.select('path')
.attr('d', pathData);
var symbolGenerator = d3.symbol() .type(d3.symbolStar) .size(80); var points = [ [0, 80], [100, 100], [200, 30], [300, 50], [400, 40], [500, 80] ]; var pathData = symbolGenerator(); d3.select('g') .selectAll('path') .data(points) .enter() .append('path') .attr('transform', function(d) { return 'translate(' + d + ')'; }) .attr('d', pathData);

D3包含了下面的symbol types:
var symbolGenerator = d3.symbol() .size(100); var symbolTypes = ['symbolCircle', 'symbolCross', 'symbolDiamond', 'symbolSquare', 'symbolStar', 'symbolTriangle', 'symbolWye']; var xScale = d3.scaleLinear().domain([0, symbolTypes.length - 1]).range([0, 700]); d3.select('g') .selectAll('path') .data(symbolTypes) .enter() .append('path') .attr('transform', function(d, i) { return 'translate(' + xScale(i) + ', 0)'; }) .attr('d', function(d) { symbolGenerator .type(d3[d]); return symbolGenerator(); }); d3.select('g') .selectAll('text') .data(symbolTypes) .enter() .append('text') .attr('transform', function(d, i) { return 'translate(' + xScale(i) + ', 40)'; }) .text(function(d) { return 'd3.' + d; });

