前文講過了D3的數據驅動機制,中間所舉的例子都很簡單。例如那個demo裏面,綁定的數據是一個簡單的數組,實現的圖元也僅僅是一堆用SVG畫的circle。可是現實世界中咱們每每會遇到複雜的需求,例如我就遇到了這樣一個需求:數據是一個複雜的對象數組,而與之綁定的圖元是一個可變圖形。該圖形能夠根據與他綁定的數據中的具體參數,在圓形、方塊、三角之間切換,而且要求過渡天然。html
面對這個需求,最直接的作法是把圓形、方塊、三角用SVG的<circle>圓形標籤,<rect>矩形標籤以及<polygon>多邊形標籤來分別實現。具體用D3實現,就是建立一個<g>集合標籤做爲數據綁定對象,根據數據參數的變化,remove裏面的原有圖形,add新的圖形,從而實現數據驅動的圖形更新。可是這種方法有一個很難解決的問題,就是圖形切換時的過渡動畫很難平滑。由於每一個圖形都是獨立的標籤,除了採用淡入淡出之類的過渡方法外,很難想到有什麼更好的過渡效果。而且出於對代碼簡化的考慮,g標籤內包裹其餘的圖形標籤的方式,也增長了複雜度。git
那麼還有其餘方案嗎?若是能有一個萬能圖形標籤來來實現各類圖形,把數據直接綁定給它,那就再好不過了。事實上SVG的<path>路徑標籤就能夠實現這一點。在我以前的文章」d3可視化實戰01:理解SVG元素特性「中已經提到,路徑功能很是強大幾乎能夠描繪任何圖形。惟一的問題是,如何指定Path的具體參數。對於通常狀況,咱們能夠本身設定參數。而在這裏,我打算用一個數學模型來指定path的具體參數,那就是superfomula超級方程式。最終實現的效果果真很是好,動畫過渡很是平滑,圖元自己也很簡單。下面讓咱們看看這個superfomula到底是何方神聖吧。github
咱們知道,不少數學函數均可以用解析式的方式表示,亦可根據變量和自變量的值在不一樣座標系下繪成各類圖形。Johan Gielis博士提出了一個函數,能夠描述天然界中發現的衆多複雜圖形和曲線,它就是超級方程式superfomula。數組
超級方式的解析式以下:svg
在極座標系下,r表明半徑,表明角度,a,b,m,n1,n2,n3是可變參數。經過調整參數的值,就能夠繪出各類圖形。下圖展現了a=b=1的狀況下,m,n1,n2,n3取不一樣值的時候superfomula所展現的圖形:函數
只經過控制這些參數,就能在極座標系下繪製如此不一樣的各類2D圖形,是否是很神奇?這就是數學這一天然科學的王冠學科的魅力。學習
關於superfomula的更多介紹:動畫
superfomula的發表者Johan Gielis博士(1962-)本來的研究方向是園藝工程和生物學。在他的早期研究階段他就感興趣於使用數學模型表徵生物生長性狀。1994年發表的論文中他就開始開始使用曲線描述天然形狀,在1997年的論文中他發現廣義的曲線模型適用於任何對稱形狀。在2003年發表於美國植物學雜誌上的論文A generic geometric transformation that unifies a large range of natural and abstract shapes中,他提出了superfomula。superfomula是超級橢圓公式superellipse的擴展,但具備更普遍的實用性。此後數百篇論文都引用了superfomula,而且以superfomula爲指導的計算機繪圖程序也隨之出現。2004年johan Gielis博士收到了InterTech技術創新卓越獎,該獎主要頒發給對平面藝術及相關產業產生重大影響的技術發明。superfomula從生物技術研究中誕生,在數學領域中昇華,並最終應用到計算機、平面設計等領域。可謂是跨學科研究應用的最好案例之一。this
superfomula也能夠擴展到3維,4維甚至更多維度。例如3維狀況下,圖形能夠經過兩個superfomula r1, r2來生成。其極座標系與笛卡爾座標系之間轉換關係爲:spa
其中, 變量值域爲[ -π/2 , π/2] (latitude維度), θ變量值域爲 [ -π, π] (longitude精度).
在github上,已經有人用D3實現了基於superfomula的圖形繪製程序。你們請點擊這裏:http://bl.ocks.org/mbostock/1021103. 該程序的關鍵是基於D3的superfomula開源插件。本着學習的目的,這裏保存了該插件的源碼,你能夠複製它而後保存爲d3-superfomula.js來使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
(function() {
var _symbol = d3.svg.symbol(),
_line = d3.svg.line();
d3.superformula = function() {
var type = _symbol.type(),
size = _symbol.size(),
segments = size,
params = {};
function superformula(d, i) {
var n, p = _superformulaTypes[type.call(this, d, i)];
for (n in params) p[n] = params[n].call(this, d, i);
return _superformulaPath(p, segments.call(this, d, i), Math.sqrt(size.call(this, d, i)));
}
superformula.type = function(x) {
if (!arguments.length) return type;
type = d3.functor(x);
return superformula;
};
superformula.param = function(name, value) {
if (arguments.length < 2) return params[name];
params[name] = d3.functor(value);
return superformula;
};
// size of superformula in square pixels
superformula.size = function(x) {
if (!arguments.length) return size;
size = d3.functor(x);
return superformula;
};
// number of discrete line segments
superformula.segments = function(x) {
if (!arguments.length) return segments;
segments = d3.functor(x);
return superformula;
};
return superformula;
};
function _superformulaPath(params, n, diameter) {
var i = -1,
dt = 2 * Math.PI / n,
t,
r = 0,
x,
y,
points = [];
while (++i < n) {
t = params.m * (i * dt - Math.PI) / 4;
t = Math.pow(Math.abs(Math.pow(Math.abs(Math.cos(t) / params.a), params.n2)
+ Math.pow(Math.abs(Math.sin(t) / params.b), params.n3)), -1 / params.n1);
if (t > r) r = t;
points.push(t);
}
r = diameter * Math.SQRT1_2 / r;
i = -1; while (++i < n) {
x = (t = points[i] * r) * Math.cos(i * dt);
y = t * Math.sin(i * dt);
points[i] = [Math.abs(x) < 1e-6 ? 0 : x, Math.abs(y) < 1e-6 ? 0 : y];
}
return _line(points) + "Z";
}
var _superformulaTypes = {
asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1},
butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1},
circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1},
clover: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
cloverFour: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1},
cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1},
diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1},
drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1},
ellipse: {m: 4, n1: 2, n2: 2, n3: 2, a: 9, b: 6},
gear: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1},
heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18},
heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1},
pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1},
rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1},
roundedStar: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1},
square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1},
star: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1},
triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1}
};
d3.superformulaTypes = d3.keys(_superformulaTypes);
})();
|