ワタシのサイトでの紹介だけに基いてこれら(jQuery のチャート系プラグイン)について知ろうとしている人であっても、注意深い人ならとっくに気付いているはずなんだけれど、D3.js は実は既に「使っている」。Built on top of d3.js なライブラリが非常に多いからだ。アタシがこれまで触れてきたものでは plotly.js、Cytoscape.js、Cubism.js がそう。
ワタシももちろん plotly.js を見つけたその日に真っ先に気付いたし、20 best JavaScript charting libraries で紹介されてるのももちろん気付いてた。ですぐさま公式サイトに飛んでみて、印象を確かめて、「究極の低レベルインフラだよなこれは」と即座に理解したので、検証も紹介も控えてた。
20 best JavaScript charting libraries の著者はすごく誠実な人だね、ほかの紹介サイトとは一味も二味も違う。自分で公式サイトに行ってみて上述の第一印象を持ってからこの紹介サイトの文章を初めて読んだが、とてもポイントをおさえた良い紹介文だ:
It conforms to the W3C web standards and is compatible across browsers. Developers tend to love it for the range of features it brings, things like “Enter and Exit” and powerful transitions. You can see some examples built with D3 over here.
The caveat is that it does not ship with pre-built charts, and has a pretty steep learning curve to get even basic charts up. But developers are an innovative bunch and have come up with quite a few out-of-the-box charting libraries, based on D3. We’ll cover some of the best ones later.
ワタシが「xx ベストほにゃらら」サイトを毛嫌いするのは「観点がない」ことだが、この著者はそうではない。誠実、というのはこういうことを言う。
ワタシや 20 best… の著者が言ってることは、実際に公式サイトに行ってみればわかると思うよ。ここな:
このギャラリーが、「凄まじい」わけである。で、「すんげー、こんなことも出来るのか!!」と大喜びするわけよ。けどさ、例えばワタシが plotly.js で苦労してみせた Scatterplot Matrix も一撃で出来るのね! と思ったらダメ。こんなだよ:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
5 <link type="text/css" rel="stylesheet" href="style.css"/>
6 <style type="text/css">
7
8 svg {
9 font-size: 14px;
10 }
11
12 .axis {
13 shape-rendering: crispEdges;
14 }
15
16 .axis line {
17 stroke: #ddd;
18 stroke-width: .5px;
19 }
20
21 .axis path {
22 display: none;
23 }
24
25 rect.extent {
26 fill: #000;
27 fill-opacity: .125;
28 stroke: #fff;
29 }
30
31 rect.frame {
32 fill: #fff;
33 fill-opacity: .7;
34 stroke: #aaa;
35 }
36
37 circle {
38 fill: #ccc;
39 fill-opacity: .5;
40 }
41
42 .legend circle {
43 fill-opacity: 1;
44 }
45
46 .legend text {
47 font-size: 18px;
48 font-style: oblique;
49 }
50
51 .cell text {
52 pointer-events: none;
53 }
54
55 .setosa {
56 fill: #800;
57 }
58
59 .versicolor {
60 fill: #080;
61 }
62
63 .virginica {
64 fill: #008;
65 }
66
67 </style>
68 </head>
69 <body>
70 <h2 style="font-size:24px;">
71 Edgar Anderson’s <i>Iris</i> data set<br>
72 scatterplot matrix
73 </h2>
74 <script type="text/javascript" src="d3/d3.js"></script>
75 <script type="text/javascript" src="d3/d3.csv.js"></script>
76 <script type="text/javascript">
77
78 d3.csv("iris.csv", function(flowers) {
79
80 // Size parameters.
81 var size = 140,
82 padding = 10,
83 n = 4,
84 traits = ["sepal length", "sepal width", "petal length", "petal width"];
85
86 // Position scales.
87 var x = {}, y = {};
88 traits.forEach(function(trait) {
89 // Coerce values to numbers.
90 flowers.forEach(function(d) { d[trait] = +d[trait]; });
91
92 var value = function(d) { return d[trait]; },
93 domain = [d3.min(flowers, value), d3.max(flowers, value)],
94 range = [padding / 2, size - padding / 2];
95 x[trait] = d3.scale.linear().domain(domain).range(range);
96 y[trait] = d3.scale.linear().domain(domain).range(range.reverse());
97 });
98
99 // Axes.
100 var axis = d3.svg.axis()
101 .ticks(5)
102 .tickSize(size * n);
103
104 // Brush.
105 var brush = d3.svg.brush()
106 .on("brushstart", brushstart)
107 .on("brush", brush)
108 .on("brushend", brushend);
109
110 // Root panel.
111 var svg = d3.select("body").append("svg:svg")
112 .attr("width", 1280)
113 .attr("height", 800)
114 .append("svg:g")
115 .attr("transform", "translate(359.5,69.5)");
116
117 // Legend.
118 var legend = svg.selectAll("g.legend")
119 .data(["setosa", "versicolor", "virginica"])
120 .enter().append("svg:g")
121 .attr("class", "legend")
122 .attr("transform", function(d, i) { return "translate(-179," + (i * 20 + 594) + ")"; });
123
124 legend.append("svg:circle")
125 .attr("class", String)
126 .attr("r", 3);
127
128 legend.append("svg:text")
129 .attr("x", 12)
130 .attr("dy", ".31em")
131 .text(function(d) { return "Iris " + d; });
132
133 // X-axis.
134 svg.selectAll("g.x.axis")
135 .data(traits)
136 .enter().append("svg:g")
137 .attr("class", "x axis")
138 .attr("transform", function(d, i) { return "translate(" + i * size + ",0)"; })
139 .each(function(d) { d3.select(this).call(axis.scale(x[d]).orient("bottom")); });
140
141 // Y-axis.
142 svg.selectAll("g.y.axis")
143 .data(traits)
144 .enter().append("svg:g")
145 .attr("class", "y axis")
146 .attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
147 .each(function(d) { d3.select(this).call(axis.scale(y[d]).orient("right")); });
148
149 // Cell and plot.
150 var cell = svg.selectAll("g.cell")
151 .data(cross(traits, traits))
152 .enter().append("svg:g")
153 .attr("class", "cell")
154 .attr("transform", function(d) { return "translate(" + d.i * size + "," + d.j * size + ")"; })
155 .each(plot);
156
157 // Titles for the diagonal.
158 cell.filter(function(d) { return d.i == d.j; }).append("svg:text")
159 .attr("x", padding)
160 .attr("y", padding)
161 .attr("dy", ".71em")
162 .text(function(d) { return d.x; });
163
164 function plot(p) {
165 var cell = d3.select(this);
166
167 // Plot frame.
168 cell.append("svg:rect")
169 .attr("class", "frame")
170 .attr("x", padding / 2)
171 .attr("y", padding / 2)
172 .attr("width", size - padding)
173 .attr("height", size - padding);
174
175 // Plot dots.
176 cell.selectAll("circle")
177 .data(flowers)
178 .enter().append("svg:circle")
179 .attr("class", function(d) { return d.species; })
180 .attr("cx", function(d) { return x[p.x](d[p.x]); })
181 .attr("cy", function(d) { return y[p.y](d[p.y]); })
182 .attr("r", 3);
183
184 // Plot brush.
185 cell.call(brush.x(x[p.x]).y(y[p.y]));
186 }
187
188 // Clear the previously-active brush, if any.
189 function brushstart(p) {
190 if (brush.data !== p) {
191 cell.call(brush.clear());
192 brush.x(x[p.x]).y(y[p.y]).data = p;
193 }
194 }
195
196 // Highlight the selected circles.
197 function brush(p) {
198 var e = brush.extent();
199 svg.selectAll(".cell circle").attr("class", function(d) {
200 return e[0][0] <= d[p.x] && d[p.x] <= e[1][0]
201 && e[0][1] <= d[p.y] && d[p.y] <= e[1][1]
202 ? d.species : null;
203 });
204 }
205
206 // If the brush is empty, select all circles.
207 function brushend() {
208 if (brush.empty()) svg.selectAll(".cell circle").attr("class", function(d) {
209 return d.species;
210 });
211 }
212
213 function cross(a, b) {
214 var c = [], n = a.length, m = b.length, i, j;
215 for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
216 return c;
217 }
218 });
219
220 </script>
221 </body>
222 </html>
「has a pretty steep learning curve to get even basic charts up」の意味はすぐにわかるであろう?
逆に言えば、plotly.js や Cytoscape.js がターゲットとしていないようなチャートを描きたい場合だって、ないことはないであろう? そういった場合に、D3.js のレベルに下りてしまって この凄まじ過ぎるギャラリーから自分がやりたいことに近いものを選んで「猿真似を試みる」ということは、考えてもいいんだよね(当面やらないけどいつかやるかもしんない)。jQuery: Cytoscape.js お試せたで前置きしたように、この手のを「手書きで javascript をゴリゴリ書く」わきゃぁなくて、何かテンプレートエンジンの類の力をどうせ借りるわけだよ、その場合「一回だけちゃんと理解する」必要はあっても、永遠に記憶しとく必要はないわけさ。
ギャラリーにはとかく興味をひくものが多いんだけれど、これはやってみたいなぁと思った:
画像キャプチャだけだと伝わらないので、是非実際にここを訪れてみて。マウスホイールを動かしてみるのだぞ。