D3 Tips and Tricks PDF
D3 Tips and Tricks PDF
D3 Tips and Tricks PDF
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have the right book and build
traction once you do.
2013 - 2014 Malcolm Maclean
Contents
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Make sure you get the most up to date copy of D3 Tips and Tricks . . . . . . . . . . . .
1
1
What is d3.js? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4
4
5
5
6
7
7
8
9
9
9
10
10
10
11
11
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
15
16
18
20
21
24
28
33
37
37
39
41
CONTENTS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
42
42
50
52
60
60
61
63
65
67
67
68
69
70
72
73
73
75
79
81
85
87
89
89
91
91
92
96
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
98
98
103
104
105
106
108
109
110
111
112
113
114
114
115
115
116
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
CONTENTS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Styles . . . . .
fill . . .
stroke .
opacity .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
fill-opacity .
.
.
.
.
.
stroke-opacity .
stroke-width . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
stroke-dasharray
. . . .
stroke-linecap .
. . . .
stroke-linejoin
. . . .
writing-mode . .
. . . .
glyph-orientation-vertical . . . .
Using styles in Cascading Style Sheets
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
rotate(a))
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
116
117
118
118
119
120
121
122
123
124
124
125
126
130
131
132
134
135
137
138
139
140
141
142
143
144
145
147
149
150
151
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
154
154
157
158
158
159
161
161
163
164
166
168
172
174
CONTENTS
Bitmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Vector Graphics (Specifically SVG) . . . . . . . . . . . . . . . . . .
Lets get exporting! . . . . . . . . . . . . . . . . . . . . . . . . . . .
Copying the image off the web page . . . . . . . . . . . . .
Open the SVG Image and Edit . . . . . . . . . . . . . . . . .
Saving as a bitmap . . . . . . . . . . . . . . . . . . . . . . .
Add an HTML table to your graph . . . . . . . . . . . . . . . . . . . . .
HTML Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
First the CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Now the d3.js code . . . . . . . . . . . . . . . . . . . . . . . . . . .
A small but cunning change . . . . . . . . . . . . . . . . . . . . .
Explaining the d3.js code (reloaded). . . . . . . . . . . . . . . . . .
Wrap up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
More table madness: sorting, prettifying and adding columns . . . . . .
Add another column of information: . . . . . . . . . . . . . . . . .
Sorting on a column . . . . . . . . . . . . . . . . . . . . . . . . . .
Prettifying (actually just capitalising the header for each column) .
Add borders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Adding web links to d3.js objects . . . . . . . . . . . . . . . . . . . . . .
Its all about the a and the xlink . . . . . . . . . . . . . . . . . .
Adding in the links . . . . . . . . . . . . . . . . . . . . . . . . . .
Making the mouse pointer ignore an object . . . . . . . . . . . . .
Understanding JavaScript Object Notation (JSON) . . . . . . . . . . . .
Using Plunker for development and hosting your D3 creations. . . . . .
Manipulating data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
How to use data imported from a csv file with spaces in the header.
Extracting data from a portion of a string. . . . . . . . . . . . . . .
Grouping and summing data (d3.nest). . . . . . . . . . . . . . . . .
Bar Charts . . . . . . . .
The data . . . . . . . .
The code . . . . . . . .
The bar chart explained
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
174
176
177
178
178
179
182
183
184
184
186
187
189
190
190
191
192
193
196
197
197
198
200
204
209
209
210
211
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
213
213
214
216
Sankey Diagrams . . . . . . . . . . . . . . . . . . . . . . . . .
What is a Sankey Diagram? . . . . . . . . . . . . . . . . . .
How d3.js Sankey Diagrams want their data formatted . . . .
Description of the code . . . . . . . . . . . . . . . . . . . . .
Formatting data for Sankey diagrams . . . . . . . . . . . . .
From a JSON file with numeric link values . . . . . . . .
From a JSON file with links as names . . . . . . . . . . .
From a CSV with source, target and value info only.
From MySQL as link information (only automatically). .
Sankey diagram case study . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
221
221
222
223
235
235
236
239
242
245
CONTENTS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
250
260
264
267
270
272
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
274
274
278
279
290
293
296
Bullet Charts . . . . . . . . . . . . . . . . . . . . .
Introduction to bullet chart structure . . . . . .
D3.js code for bullet charts . . . . . . . . . . . .
Adapting and changing bullet chart components
Understand your data . . . . . . . . . . . .
Add as many individual charts as you want.
Add more ranges and measures . . . . . . .
Updating a bullet chart automatically . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
298
298
299
306
306
307
307
309
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
312
312
317
318
322
323
324
326
327
331
331
332
339
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
346
346
347
348
350
351
351
352
352
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
CONTENTS
Bubble Chart . . . . . . . . . . . . . . . . . . . .
Geo Choropleth Chart . . . . . . . . . . . . . . .
Data Table . . . . . . . . . . . . . . . . . . . . .
Bare bones structure for dc.js and crossfilter page . . .
Add a Bar Chart. . . . . . . . . . . . . . . . . . . . .
Position the bar chart . . . . . . . . . . . . . . .
Assign the bar chart type . . . . . . . . . . . . .
Dimension and group the bar chart data . . . . .
Configure the bar chart parameters . . . . . . . .
Just one more thing . . . . . . . . . . . . . . .
Just yet another thing . . . . . . . . . . . . . .
Position the chart . . . . . . . . . . . . . .
Assign type . . . . . . . . . . . . . . . . .
Dimension and Group . . . . . . . . . . .
Configure chart parameters . . . . . . . .
Add a Line Chart. . . . . . . . . . . . . . . . . . . . .
Position the line chart . . . . . . . . . . . . . . .
Assign the line chart type . . . . . . . . . . . . .
Dimension and group the line chart data . . . . .
Configure the line chart parameters . . . . . . .
Adding tooltips to a line chart . . . . . . . . . . . . .
Add a Row Chart. . . . . . . . . . . . . . . . . . . . .
Position the row chart . . . . . . . . . . . . . . .
Assign the row chart type . . . . . . . . . . . . .
Dimension and group the row chart data . . . . .
Configure the row chart parameters . . . . . . .
Add a Pie Chart. . . . . . . . . . . . . . . . . . . . . .
Position the pie chart . . . . . . . . . . . . . . .
Assign the pie chart type . . . . . . . . . . . . .
Dimension and group the pie chart data . . . . .
Configure the pie chart parameters . . . . . . . .
Resetting filters . . . . . . . . . . . . . . . . . . . . .
Making the reset label a little bit better behaved.
Reset all the charts . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
352
353
353
355
364
364
365
365
366
370
370
371
371
371
371
373
373
374
374
375
378
381
382
383
383
384
388
389
390
390
391
394
395
397
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
401
401
402
403
404
406
406
408
409
411
412
CONTENTS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
422
422
422
422
422
426
431
433
436
438
438
438
440
440
441
441
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
443
443
444
445
445
447
450
Appendices . . . . . . . . . . . . .
Simple Line Graph . . . . . . .
Graph with Many Features . . .
Graph with Area Gradient . . .
Bar Chart . . . . . . . . . . . .
Linking Objects . . . . . . . . .
PHP with MySQL Access . . . .
Simple Sankey Graph . . . . . .
Simple Tree Diagram . . . . . .
Interactive Tree Diagram . . . .
Force Layout Diagram . . . . .
Bullet Chart . . . . . . . . . . .
Map with zoom / pan and cities
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
452
452
454
458
461
464
466
467
470
473
478
483
486
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Acknowledgements
First and foremost I would like to express my thanks to Mike Bostock, the driving force behind
d3.js. His efforts are tireless and his altruism in making his work open and available to the masses
is inspiring.
Mike has worked with a crew of like-minded individuals in bringing D3 to the World. Vadim
Ogievetsky and Jeffrey Heer share honours for the work on D3: Data-Driven Documents and
while there has been a cast of over 40 people contributing to the D3 code base, Jason Davies
stands out as the man who has provided a generous portion especially in the area of mapping.
Nick Zhu has created a fantastic resource in dc.js (which is built on top of d3.js and crossfilter)
and has been kind enough to provide good advice and permission to include some of his work
in the dc.js section.
Advice given by Christophe Viau has been a great help in getting me settled into the on-line
world and his energy in managing and directing the D3 community is amazing.
Mike Dewar (Getting Started with D3), Scott Murray (Interactive Data Visualization for the Web)
and Sebastian Gutierrez (dashingd3js.com) lead the pack for providing high quality reference
material for learning D3. Many thanks gentlemen.
I am particularly grateful for the assistance given by Filiep Spyckerelle and Robin Bennett who
selflessly donated their time and expertise in proofreading above and beyond the call of duty
(where this document contains any errors, they are most certainly mine). Big thanks go out to
the D3 community. Whether providing advice on Google Groups or Stack Overflow, contributing
examples on bl.ocks.org or just giving back in the form of time and effort to similar work. Well
done all.
Lastly, I want to pay homage to Leanpub who have made the publishing of this document
possible. They offer an outstanding service for self-publishing and have made the task of
providing and distributing content achievable.
What is d3.js?
d3.js (hereafter abridged as D3) is a JavaScript library for manipulating documents based on
data.
But that description doesnt do it justice.
D3 is all about helping you to take information and make it more accessible to others via a web
browser.
Its a JavaScript library. That means that its a tool that can be used in conjunction with other
tools to get a job done. Those other tools are mainly HTML and CSS (amongst others) but you
dont need to know too much about either to use D3 (although it will help :-)).
Its an open framework, which means that there are no hidden mysteries about how it does its
magic and it allows others to contribute to a constant cycle of improvement.
Its built to leverage web standards which means that modern browsers dont have to do anything
special to use D3, they just have to support the framework that the Internet has adopted for ease
of use.
The beauty of D3 is that it allows you to associate data and what appears on the screen in a way
that directly links the two. Change the data and you change the object on the screen. D3s trick
is to let you set what appears on the screen. A circle, a line, a point on a map, a graph, a bouncing
ball, a gradient (and way, way more). Once the data and the object are linked the possibilities
are endless.
It wont do everything for you in your quest to create the perfect visualization, but it does give
you the ability to achieve that goal.
It bridges the gap between the static display of data and the desire of people to mess about with
it. That applies equally to the developer who wants to show something cool and to the end user
who wants to be able to explore information interactively.
It was (and still is being) developed by Mike Bostock who has not just spent time writing
the code, but writing the documentation for D3 as well. There is an extensive community of
supporters who also contribute to the code, provide technical support online and generally
have fun creating amazing visualizations. Their contributions are extraordinary (you only have
to look at the work of Jason Davies to be amazed).
http://d3js.org/
http://bost.ocks.org/mike/
https://github.com/mbostock/d3/wiki
https://groups.google.com/forum/?fromgroups#!forum/d3-js
http://stackoverflow.com/questions/tagged/d3.js
https://github.com/mbostock/d3/wiki/Gallery
Introduction
I never set out to write treatise on D3
I am a simple user of this extraordinary framework and when I say simple, I really mean I
had no idea how to get it to do anything when I started; I needed to do a lot of searching and
learned by trial-and-error (emphasis on the errors which were entirely mine). The one thing that
I did know was that the example graphics shown by Mike Bostock and others were the sort of
graphical goodness that I wanted to play with.
So to get from the point of having no skills whatsoever to the point where I could begin to code
up something to display data in a way I wanted, I had to capture the information as I went. The
really cool thing about this sort of process is that it doesnt need to occur all at once. You can
start with no knowledge whatsoever (or pretty close) and by standing on the shoulders of others
work, you can add building blocks to improve what youre seeing and then change the blocks to
adapt and improve.
For example (and this is pretty much how it started). I wanted to draw a line graph, so I imported
an example and then got it running locally on my computer. Then I worked out how to change
the example data for my data. Then I worked out how to move the Y axis from the right to the
left. Then how to make the axis labels larger, change the tick size, make the lines fatter, change
the colour, add a label, fill the area under the graph, put the graph in the centre of the page, add a
glow to the text to help it stand out, put it in a framework (bootstrap), add buttons to change data
sets, animate the transitions between data sets, update the data automatically when it changed,
add a pan and zoom feature, turn parts of the graph into hyperlinks to move to other graphs
And then I started on bar graphs :-).
The point to take away from all of this is that any one graph is just a collection of lots of blocks
of code, each block designed to carry out a specific function. Pick the blocks you want and
implement them.
I found it was much simpler to work on one thing (block) at a time, and this helped greatly to
reduce the uncertainty factor when things didnt work as anticipated. Im not going to pretend
that everything Ive done while trying to build graphs employs the most elegant or efficient
mechanism, but in the end, if it all works on the screen, I walk away happy :-). Thats not to
say I have deliberately ignored any best practices I just never knew what they were. Likewise,
wherever possible, I have tried to make things as extensible as possible.
You will find that I have typically eschewed a simple Do this approach for more of a story
telling exercise. This means that some explanations are longer and more flowery than might be
to everyones liking, but there you go, try to be brave :-)
Im sure most authors try to be as accessible as possible. Id like to do the same, but be warned
Theres a good chance that if you ask me a technical question I may not know the answer. So
please be gentle with your emails :-).
Email: [email protected]
HTML
This stands for HyperText Markup Language and is the stuff that web pages are made of. Check
out the definition and other information on Wikipedia for a great overview. Just remember
that all youre going to use HTML for is to hold the code that you will use to present your
information. This will be as a .html (or .htm) file and they can be pretty simple (well look at
some in a moment).
JavaScript
JavaScript is whats called a scripting language. It is the code that will be contained inside the
HTML file that will make D3 do all its fanciness. In fact, D3 is a JavaScript Library, its the native
language for using D3.
Knowing a little bit about this would be really good, but to be perfectly honest, I didnt know
anything about it before I started. I read a book along the way (JavaScript: The Missing Manual
http://en.wikipedia.org/wiki/HTML
http://en.wikipedia.org/wiki/JavaScript
http://shop.oreilly.com/product/9780596515898.do
from OReilly) and that helped with context, but the examples that are available for D3 graphics
are understandable, and with a bit of trial and error, you can figure out whats going on.
In fact, most of what this collection of informations about is providing examples and explanations for the JavaScript components of D3.
Full disclosure
I know CSS is a ridiculously powerful tool that would make my life easier, but I use it
in a very basic (and probably painful) way. Dont judge me, just accept that the way
Ive learnt was what I needed to get the job done (this probably means that noobs like
myself will find it easier, but where possible try and use examples that include what
look like logical CSS structures)
Web Servers
Ok, this can go one of two ways. If you have access to a web server and know where to put the
files so that you can access them with your browser, youre on fire. If youre not quite sure, read
on
http://en.wikipedia.org/wiki/Css
A web server will allow you to access your HTML files and will provide the structure that allows
it to be displayed on a web browser. There are some simple instructions on the main D3 wiki
page for setting up a local server. Or you might have access to a remote one and be able to
upload your files. However, for a little more functionality and a whole lot of ease of use, I can
thoroughly recommend WampServer as a free and simple way to set up a local web server that
includes PHP and a MySQL database (more on those later). Go to the WampServer web page
(http://www.wampserver.com/en/) and see if it suits you.
Throughout this document I will be describing the files and how theyre laid out in a way that
has suited my efforts while using WAMP, but they will work equally well on a remote server. I
will explain a little more about how I arrange the files later in the Getting D3 section.
There are other options of course. You could host code on GitHub and present the resulting
graphics on bl.ocks.org. This is a great way to make sure that your code is available for peer
review and sharing with the wider community.
One such alternative option that I have recently started playing with is Plunker (http://plnkr.co/)
This is a lightweight collaborative online editing tool. Its so cool I wrote a special section for
it which you can find later in this document. This is definitely worth trying if you want to use
something simple without a great deal of overhead. If you like what you see, perhaps consider
an alternative that provides a greater degree of capability if you go on to greater d3.js things.
PHP
PHP is a scripting language for the web. That is to say that it is a programming language which
is executed when you load web pages and it helps web pages do dynamic things.
You might think that this sounds familiar and that JavaScript does the same thing. But not quite.
JavaScript is designed so that it travels with the web page when it is downloaded by a browser
(the client). However, PHP is executed remotely on the server that supplies the web page. This
might sound a bit redundant, but its a big deal. This means that the PHP which is executed
doesnt form part of the web page, but it can form the web page. The implication here is that the
web page you are viewing can be altered by the PHP code that runs on a remote server. This is
the dynamic aspect of it.
https://github.com/mbostock/d3/wiki
https://github.com/about
http://bl.ocks.org/
In practice, PHP could be analogous to the glue that binds web pages together. Allowing different
portions of the web page to respond to directions from the end user.
It is widely recognised not only as a relatively simple language to learn, but also as a fairly
powerful one. At the same time it comes into criticism for being somewhat fragmented and
sometimes contradictory or confusing. But in spite of any perceived shortcomings, it is a very
widely used and implemented language and one for which there is no obvious better option.
Getting D3
Luckily this is pretty easy.
Go to the D3 repository on github and download the entire repository by clicking on the ZIP
button.
What you do with it from here depends on how youre hosting your graphs. If youre working
on them on your local PC, then you will want to have the d3.js file in the path that can be seen by
the browser. Again, I would recommend WAMP (a local web server) to access your files locally.
If youre using WAMP, then you just have to make sure that it knows to use a directory that will
contain the d3 directory and you will be away.
The following image is intended to provide a very crude overview of how you can set up the
directories.
webserver: Use this as your base directory where you put your files that you create. That
way when you open your browser you point to this directory and it allows you to access
the files like a normal web site.
d3: This would be your unzipped d3 directory. It contains all the examples and more
importantly the d3.v3.js file that you need to get things going. You will notice in the code
examples that follow there is a line like the following;
<script type="text/javascript" src="d3/d3.v3.js"></script>.
This tells your browser that from the file it is running (one of the graph html files) if it
goes into the d3 folder it will find the d3.v3.js file that it can load.
data: I use this directory to hold any data files that I would use for processing. For example,
you will see the following line in the code examples that follow d3.tsv("data/data.tsv",
function(error, data) {. Again, thats telling the browser to go into the data directory
and to load the data.tsv file.
https://github.com/mbostock/d3
js: Often you will find that you will want to include other JavaScript libraries to load. This
is a good place to put them.
10
Stack Overflow
Stack Overflow is a question and answer site whose stated desire is to build a library of detailed
answers to every question about programming. Ambitious. So how are they doing? Actually
really well. Stack overflow is a fantastic place to get help and information. Its also a great place
to help people out if you have some knowledge on a topic.
They have a funny scheme for rewarding users that encourages providing good answers based
on readers voting. Its a great example of gamification working well. If you want to know a little
more about how it works, check out this page; http://stackoverflow.com/about.
They have a d3.js tag (http://stackoverflow.com/questions/tagged/d3.js) and like Google Groups
there is a running list of different topics that are an excellent source of information.
Github
Github is predominantly a code repository and version control site. It is highly regarded for its
technical acumen and provides a fantastic service that is broadly used for many purposes. Not
the least of which is hosting the code (and the wiki) for d3.js.
Whilst not strictly a site that specialises in providing a Q & A function, there is a significant
number of repositories (825 at last count) which mention d3.js. With the help from an astute
search phrase, there is potentially a solution to be found there.
The other associated feature of Github is Gist. Gist is a pastebin service (a place where you can
copy and past code) that can provide a wiki like feature for individual repositories and web
pages that can be edited through a Git repository. Gist plays a role in providing the hub for the
bl.ocks.org example hosting service set up by Mike Bostock.
For a new user, Github / Gist can be slightly daunting. Its an area where you almost need to
know whats going on to know before you dive in. This is certainly true if you want to make
use of its incredible features that are available for hosting code. However, if you want to browse
other peoples code its an easier introduction. Have a look through whats available and if you
feel so inclined, I recommend that you learn enough to use their service. Its time well spent.
bl.ocks.org
bl.ocks.org is a viewer for code examples which are hosted on Gist. You are able to load your
code into Gist, and then from bl.ocks.org you can view them.
This is a really great way for people to provide examples of their work and there are many
who do. However, its slightly tricky to know what is there. There is a current project
being championed by Christophe Viau and others to provide better access to a range of D3
documentation. The early indications are that it will provide a fantastic method of accessing
examples and information. Watch that space.
I would describe the process of getting your own code hosted and displaying as something that
will be slightly challenging for people who are not familiar with Github / Gist, but again, in
https://github.com/
http://bl.ocks.org/
https://groups.google.com/forum/?fromgroups=#!topic/d3-js/g7BxBMUZP8o
11
terms of visibility of the code and providing an external hosting solution, it is excellent and well
worth the time to get to grips with.
Twitter
Twitter provides a great alerting service to inform a large disparate group of people about stuff.
Its certainly a great way to keep in touch on an hour by hour basis with people who are involved
with d3.js and this can be accomplished in a couple of ways. First, find as many people from the
various D3 sites around the web who you consider to be influential in areas you want to follow
(different aspects such as development, practical output, educational etc) and follow them. Even
better, I found it useful to find a small subset who I considered to be influential people and I
noted who they followed. Its a bit stalky if youre unfamiliar with it, but the end result should
be a useful collection of people with something useful to say.
Books
There are only a couple of books that have been released so far on d3.js.
There is Getting Started with D3 by Mike Dewar (OReilly Media, June 2012). This will
take you through a good set of exercises to develop your D3 skills and is accompanied by
downloadable examples.
There is Interactive Data Visualization for the Web by Scott Murray, (OReilly Media,
November 2012). Currently this has only been released as an ebook, but is scheduled to
be released in print form in 2013. The book is based on his great set of on-line tutorials
(http://alignedleft.com/tutorials/).
Of course, there is the original paper that launched D3 D3: Data-Driven Documents by Michael
Bostock, Vadim Ogievetsky and Jeffrey Heer (IEEE Trans. Visualization & Comp. Graphics (Proc.
InfoVis), 2011)
http://shop.oreilly.com/product/0636920025429.do
http://ofps.oreilly.com/titles/9781449339739/
Basic Graph
13
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"\
);
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
svg.append("path")
14
.attr("d", valueline(data));
svg.append("g")
// Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>
Once weve finished explaining these parts, well start looking at what we need to add in and
adjust so that we can incorporate other useful functions that are completely reusable in other
diagrams as well.
The end point being something hideous like the following;
I say hideous since the graph is not intended to win any beauty prizes, but there are several
components to it which some people may find useful (gridlines, area fill, axis label, drop shadow
for text, title, text formatting).
So, we can break the file down into component parts. Im going to play kind of fast and loose
here, but never fear, itll all make sense.
15
HTML
Heres the HTML portions of the code;
<!DOCTYPE html>
<meta charset="utf-8">
<style>
The CSS is in here
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
The D3 JavaScript code is here
</script>
</body>
Compare it with the full code. It kind of looks like a wrapping for the CSS and JavaScript. You
can see that it really doesnt boil down to much at all (that doesnt mean its not important).
There are plenty of good options for adding additional HTML stuff into this very basic part for
the file, but for what were going to be doing, we really dont need to bother too much.
One thing probably worth mentioning is the line;
<script type="text/javascript" src="d3/d3.v3.js"></script>
Thats the line that identifies the file that needs to be loaded to get D3 up and running. In this
case the file is stored in a folder called d3 which itself is in the same directory as the main html
file. The D3 file is actually called d3.v3.js which may come as a bit of a surprise. That tells us
that this is version 3 of the d3.js file (the .v3. part) which is an indication that it is separate from
the v2 release, which has recently been superseded.
Later when doing things like implementing integration with bootstrap (a pretty layout framework) we will be doing a great deal more, but for now, thats the basics done.
The two parts that we left out are the CSS and the D3 JavaScript.
16
CSS
The CSS is as follows;
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
So Cascading Style Sheets give you control over the look / feel / presentation of the content. The
idea is to define a set of properties to objects in the web page.
They are made up of rules. Each rule has a selector and a declaration and each declaration
has a property and a value (or a group of properties and values).
For instance in the example code for this web page we have the following rule;
body { font: 12px Arial;}
body is the selector. This tells you that on the web page, this rule will apply to the body of the
page. This actually applies to all the portions of the web page that are contained in the body
portion of the HTML code (everything between <body> and </body> in the HTML bit). { font:
12px Arial;} is the declaration portion of the rule. It only has the one declaration which is the
bit that is in between the curly braces. So font: 12px Arial; is the declaration. The property
is font: and the value is 12px Arial;. This tells the web page that the font that appears in the
body of the web page will be in 12 px Arial.
Sure enough if we look at the axes of the graph
17
and we get
Hmm Times font. I think we can safely say that this has had the desired effect.
So what else is there?
What about the bit thats like;
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
Well, the whole thing is one rule, path is the selector. In this case, path is referring to a line in
the D3 drawing nomenclature.
For that selector there are three declarations. They give values for the properties of stroke (in
this case colour), stroke-width (the width of the line) and fill (we can fill a path with a block
of colour).
So lets change things :-)
path {
stroke: red;
stroke-width: 5;
fill: yes;
}
18
Filling of a path
Wow! The line is now red, it looks about 5 pixels wide and its tried to fill the area (roughly
defined by the curve) with a black colour.
It aint pretty, but it certainly did change. In fact if we go;
fill: blue;
Well get
D3 JavaScript
The D3 JavaScript part of the code is as follows;
19
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"\
);
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
svg.append("g")
// Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
20
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
Again theres quite a bit of detail in the code, but its not so long that you cant work out whats
doing what.
Lets examine the blocks bit by bit to get a feel for it.
This is really (really) well explained on Mike Bostocks page on margin conventions here
http://bl.ocks.org/3019563, but at the risk of confusing you heres my crude take on it.
The first line defines the four margins which surround the block where the graph (as an object)
is positioned.
var margin = {top: 30, right: 20, bottom: 30, left: 50},
So there will be a border of 30 pixels at the top, 20 at the right and 30 and 50 at the bottom and
left respectively. Now the cool thing about how these are set up is that they use an array to define
everything. That means if you want to do calculations in the JavaScript later, you dont need to
put the numbers in, you just use the variable that has been set up. In this case margin.right = 20!
So when we go to the next line;
width = 600 - margin.left - margin.right,
the width of the inner block of the canvas where the graph will be drawn is 600 pixels
margin.left margin.right or 600-50-20 or 530 pixels wide. Of course now you have another
variable width that we can use later in the code.
Obviously the same treatment is given to height.
Another cool thing about all of this is that just because you appear to have defined separate
areas for the graph and the margins, the whole area in there is available for use. It just makes
it really useful to have areas designated for the axis labels and graph labels without having to
juggle them and the graph proper at the same time.
So, lets have a play and change some values.
http://bl.ocks.org/3019563
21
var margin = {top: 80, right: 20, bottom: 80, left: 50},
width = 400 - margin.left - margin.right,
height = 270 - margin.top margin.bottom;
Here weve made the graph narrower (400 pixels) but retained the left / right margins and
increased the top bottom margins while maintaining the overall height of the canvas. The really
cool thing that you can tell from this is that while we shrank the dimensions of the area that we
had to draw the graph in, it was still able to dynamically adapt the axes and line to fit properly.
That is the really cool part of this whole business. D3 is running in the background looking after
the drawing of the objects, while you get to concentrate on how the data looks without too much
maths!
In fact its a combination of a few bits and another piece that isnt shown!, But lets take it one
step at a time :-)
Theres lots of different ways that we can get data into our web page to turn into graphics. And
the method that youll want to use will probably depend more on the format that the data is in
than the mechanism you want to use for importing.
For instance, if its only a few points of data we could include the information directly in the
JavaScript.
That would make it look something like;
22
var data = [
{date:"1-May-12",close:"58.13"},
{date:"30-Apr-12",close:"53.98"},
{date:"27-Apr-12",close:"67.00"},
{date:"26-Apr-12",close:"89.70"},
{date:"25-Apr-12",close:"99.00"}
];
The format of the data shown above is called JSON (JavaScript Object Notation) and its a great
way to include data since its easy for humans to read whats in there and its easy for computers
to parse the data out. For a brief overview of JSON there is a separate section in the Assorted
Tips and Tricks Chapter that may assist.
But if youve got a fair bit of data or if the data you want to include is dynamic and could be
changing from one moment to the next, youll want to load it from an external source. Thats
when we call on D3s Request functions.
Request Functions
A Request is a function that instructs the browser to reach out and grab some data
from somewhere. It could be stored locally (on the web server) or somewhere out in the
Internet. There are different types of requests depending on the type of data you want
to ingest. Each type of data is formatted with different rules, so the different requests
interpret those rules to make sure that the data is returned to the D3 processing in
a format that it understands. You could therefore think of the different Requests as
translators and the different data formats as being foreign languages.
23
Now, its important to note that this is not an exclusive list of what can be ingested. If
youve got some funky data in a weird format, you can still get it in, but you will most
likely need to stand up a small amount of code somewhere else in your page to do the
conversion (we will look at this process when describing getting data from a MySQL
database).
The first line of that piece of code invokes the d3.tsv request (d3.tsv) and then the function is
pointed to the data file that should be loaded (data/data.tsv). This is referred to as the url
(unique resource locator) of the file. In this case the file is stored locally, but the url could just as
easily point to a file somewhere on the Internet.
The format of the data in the data.tsv file looks a bit like this;
date
1-May-12
30-Apr-12
27-Apr-12
26-Apr-12
25-Apr-12
close
58.13
53.98
67.00
89.70
99.00
(although the file is longer (about 26 data points)). The date and the close heading labels are
separated by a tab as are each subsequent date and number. Hence the tab separated values :-).
The next part is part of the coolness of JavaScript. With the request made and the file requested,
the script is told to carry out a function on the data (which will now be called data).
function(error, data) {
There are actually more things that get acted on as part of the function call, but the one we will
consider here is contained in the following lines;
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
This block of code simply ensures that all the numeric values that are pulled out of the tsv file
are set and formatted correctly. The first line sets the data variable that is being dealt with (called
slightly confusingly data) and tells the block of code that, for each group within the data array
it should carry out a function on it. That function is designated d.
24
data.forEach(function(d) {
The information in the array can be considered as being stored in rows. Each row consists of
two values: one value for date and another value for close.
The function is pulling out values of date and close one row at a time.
Each time it gets a value of date and close it carries out the following operations;
d.date = parseDate(d.date);
For this specific value of date being looked at (d.date), d3.js changes it into a date format that is
processed via a separate function parseDate. (The parseDate function is defined in a separate
part of the script, and we will examine that later.) For the moment, be satisfied that it takes the
raw date information from the tsv file in a specific row and converts it into a format that D3 can
then process. That value is then re-saved in the same variable space.
The next line then sets the close variable to a numeric value (if it isnt already) using the +
operator.
d.close = +d.close;
This appears to be good practice when the format of the number being pulled out of
the data may not mean that it is automagically recognised as a number. This line will
ensure that it is.
So, at the end of that section of code, we have gone out and picked up a file with data in it of a
particular type (tab separated values) and ensured that it is formatted in a way that the rest of
the script can use correctly.
Now, the astute amongst you will have noticed that in the first line of that block of code
(d3.tsv("data/data.tsv", function(error, data) {) we opened a normal bracket ( ( ) and
a curly bracket ( { ), but we never closed them. Thats because they stay open until the very end
of the file. That means that all those blocks that occur after the d3.tsv bit are referenced to the
data array. Or put another way, it uses data to draw stuff!
But anyway, lets get back to figuring what the code is doing by jumping back to the end of the
margins block.
25
be in formats that name the months or weekdays (E.g. January, Tuesday) or combine dates and
time together (E.g. 2012-12-23 15:45:32). So, if we were to attempt to try to load in some data and
to try and get D3 to recognise it as date / time information, we really need to tell it what format
the date / time is in.
Nothing too surprising here, a very simple graph (note the time scale on the x axis).
Now we will change the later date in the data.tsv file so that it is a lot closer to the starting date;
date
29-Mar-12
26-Mar-12
close
58.13
606.98
26
Ahh. Not only did we not have to make any changes to our JavaScript code, but it was able
to recognise the dates were closer and fill in the intervening gaps with appropriate time / day
values. Now, one more time for giggles.
This time well stretch the interval out by a few years.
date
29-Mar-21
26-Mar-12
close
58.13
606.98
Hopefully thats enough encouragement to impress upon you that formatting the time is a
REALLY good thing to get right. Trust me, it will never fail to impress :-).
Back to formatting.
The line in the JavaScript that parses the time is the following;
var parseDate = d3.time.format("%d-%b-%y").parse;
This line is used when the data.forEach(function(d) portion of the code (that we looked at a
couple of pages back) used d.date = parseDate(d.date) as a way to take a date in a specific
format and to get it recognised by D3. In effect it said take this value that is supposedly a date
and make it into a value I can work with.
27
The function used is the d3.time.format(specifier) function where the specifier in this case
is the mysterious combination of characters %d-%b-%y. The good news is that these are just a
combination of directives specific for the type of date we are presenting.
The % signs are used as prefixes to each separate format type and the - (minus) signs are literals
for the actual - (minus) signs that appear in the date to be parsed.
The d refers to a zero-padded day of the month as a decimal number [01,31].
The b refers to an abbreviated month name.
And the y refers to the year (without the centuries) as a decimal number.
If we look at a subset of the data from the data.tsv file we see that indeed, the dates therein are
formatted in this way.
1-May-12
30-Apr-12
27-Apr-12
26-Apr-12
25-Apr-12
58.13
53.98
67.00
89.70
99.00
Thats all well and good, but what if your data isnt formatted exactly like that?
Good news. There are multiple different formatters for different ways of telling time and you get
to pick and choose which one you want. Check out the Time Formatting page on the D3 Wiki for
a the authoritative list and some great detail, but the following is the list of currently available
formatters (from the d3 wiki);
28
%x - date, as %m/%d/%y.
%X - time, as %H:%M:%S.
%y - year without century as a decimal number [00,99].
%Y - year with century as a decimal number.
%Z - time zone offset, such as -0700.
There is also a a literal % character that can be presented by using double % signs.
As an example, if you wanted to input date / time formatted as a generic MySQL YYYY-MM-DD
HH:MM:SS TIMESTAMP format the D3 parse script would look like;
parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
From our basic web page we have now moved to the section that includes the following lines;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
The purpose of these portions of the script is to ensure that the data we ingest fits onto our
graph correctly. Since we have two different types of data (date/time and numeric values) they
need to be treated separately (but they do essentially the same job). To examine this whole
concept of scales, domains and ranges properly, we will also move slightly out of sequence and
(in conjunction with the earlier scale statements) take a look at the lines of script that occur later
and set the domain. They are as follows;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
http://www.jeromecukier.net/blog/2011/08/11/d3-scales-and-color/
29
The idea of scaling is to take the values of data that we have and to fit them into the space we
have available.
If we have data that goes from 53.98 to 636.23 (as the data we have for close in our tsv file does),
but we have a graph that is 210 pixels high (height = 270 - margin.top margin.bottom;) we
clearly need to make an adjustment.
Not only that. Even though our data goes from 53.98 to 636.23, that would look slightly misleading
on the graph and it should really go from 0 to a bit over 636.23. It sounds really complicated,
but lets simple it up a bit.
First we make sure that any quantity we specify on the x axis fits onto our graph.
var x = d3.time.scale().range([0, width]);
Here we set our variable that will tell D3 where to draw something on the x axis. By using the
d3.time.scale() function we make sure that D3 knows to treat the values as date / time entities
(with all their ingrained peculiarities). Then we specify the range that those values will cover
(.range) and we specify the range as being from 0 to the width of our graphing area (See! Setting
those variables for margins and widths are starting to pay off now!).
Then we do the same for the Y axis.
var y = d3.scale.linear().range([height, 0]);
Theres a different function call (d3.scale.linear()) but the .range setting is still there. In the
interests of drawing a (semi) pretty picture to try and explain, hopefully this will assist;
I know, I know, its a little misleading because nowhere have we actually said to D3 this is our
data from 53.98 to 636.23. All weve said is when we get the data, well be scaling it into this
space.
30
Now hang on, whats going on with the [height, 0] part in y axis scale statement? The astute
amongst you will note that for the time scale we set the range as [0, width] but for this one
([height, 0]) the values look backwards.
Well spotted.
This is all to do with how the screen is laid out and referenced. Take a look at the following
diagram showing how the coordinates for drawing on your screen work;
The top left hand of the screen is the origin or 0,0 point and as we go left or down the
corresponding x and y values increase to the full values defined by height and width.
Thats good enough for the time values on the x axis that will start at lower values and increase,
but for the values on the y axis were trying to go against the flow. We want the low values to
be at the bottom and the high values to be at the top.
No problem. We just tell D3 via the statement y = d3.scale.linear().range([height, 0]);
that the larger values (height) are at the low end of the screen (at the top) and the low values
are at the bottom (as you most probably will have guessed by this stage, the .range statement
uses the format .range([closer_to_the_origin, further_from_the_origin]). So when we
put the height variable first, that is now associated at the top of the screen.
Weve scaled our data to the graph size and ensured that the range of values is set appropriately.
Whats with the domain part that was in this sections title?
Come on, you remember this little piece of script dont you?
31
While it exists in a separate part of the file from the scale / range part, it is certainly linked.
Thats because theres something missing from what we have been describing so far with the set
up of the data ranges for the graphs. We havent actually told D3 what the range of the data is.
Thats also the reason this part of the script occurs where it does. It is within the portion where
the data.tsv file has been loaded as data and its therefore ready to use it.
So, the .domain function is designed to let D3 know what the scope of the data will be. This is
what is then passed to the scale function.
Looking at the first part that is setting up the x axis values, it is saying that the domain for the x
axis values will be determined by the d3.extent function which in turn is acting on a separate
function which looks through all the date values that occur in the data array. In this case the
.extent function returns the minimum and maximum value in the given array.
function(d) { return d.date; } returns all the date values in data. This is then passed
to
The .extent function that finds the maximum and minimum values in the array and
then
The .domain function which returns those maximum and minimum values to D3 as the
range for the x axis.
Pretty neat really. At first you might think it was overly complex, but breaking the function
down into these components allows additional functionality with differing scales, values and
quantities. In short, dont sweat it. Its a good thing.
The x axis values are dates; so the domain for them is basically from the 26th of March 2012 till
1st of May 2012. The y axis is done slightly differently
y.domain([0, d3.max(data, function(d) { return d.close; })]);
Because the range of values desired on the y axis goes from 0 to the maximum in the data range,
thats exactly what we tell D3. The 0 in the .domain function is the starting point and the
finishing point is found by employing a separate function that sorts through all the close values
in the data array and returns the largest one. Therefore the domain is from 0 to 636.23.
Lets try a small experiment. Lets change the y axis domain to use the .extent function (the same
way the x axis does) to see what it produces.
The JavaScript for the y domain will be;
y.domain(d3.extent(data, function(d) { return d.close; }));
32
You can see apart from a quick copy paste of the internals, all I had to change was the reference
to close rather than date.
And the result is
Look at that! The starting point for the y axis looks like its pretty much on the 53.98 mark and
the graph itself certainly touches the x axis where the data would indicate it should.
Now, Im not really advocating making a graph like this since I think it looks a bit nasty (and a
casual observer might be fooled into thinking that the x axis was at 0). However, this would be a
useful thing to do if the data was concentrated in a narrow range of values that are quite distant
from zero.
For instance, if I change the data.tsv file so that the values are represented like the following;
Then it kind of loses the ability to distinguish between values around the median of the data.
But, if I put in our magic .extent function for the y axis and redraw the graph
33
Ive included both the x and y axes because they carry out the formatting in very similar ways.
Its worth noting that this is not the point where the axes get drawn. That occurs later in the
piece where the data.tsv file has been loaded as data.
D3 has its own axis component that aims to take the fuss out of setting up and displaying the
axes. So it includes a number of configurable options.
Looking first at the x axis;
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
The axis function is called with d3.svg.axis(). Then the scale is set using the x values that we
set up in the scales, ranges and domains section using .scale(x). Then a curious thing happens,
we tell the graph to orientate itself to the bottom of the graph .orient("bottom"). If I tell you
that bottom is the default setting, then you could be forgiven for thinking that technically, we
dont need to specify this since it will go there anyway, but it does give us an opportunity to
change it to "top" to see what happens;
34
Well, I hope you didnt see that coming, because I didnt. It transpires that what were talking
about there is the orientation of the values and ticks about the axis line itself. Ahh Ok. Useful
if your x axis is at the top of your graph, but for this one? Not so useful.
The next part (.ticks(5)) sets the number of ticks on the axis. Hopefully you just did a quick
count across the bottom of the previous graph and went Yep, five ticks. Spot on. Well done if
you did, but theres a little bit of a sneaky trick up D3s sleeve with the number of ticks on a
graph axis.
For instance, heres what the graph looks like when the .ticks(5) value is changed to .ticks(4).
Eh? Hang on. Isnt that some kind of mistake? There are still five ticks. Yep, sure is! But wait
we can keep dropping the ticks value till we get to two and it will still be the same. At .ticks(2)
though, we finally see a change.
35
How about that? At first glance that just doesnt seem right, then you have a bit of a think about
it and you go Hmm When there were 5 ticks, they were separated by a week each, and that
stayed that way till we got to a point where it could show a separation of a month..
D3 is making a command decision for you as to how your ticks should be best displayed. This is
great for simple graphs and indeed for the vast majority of graphs. Like all things related to D3,
if you really need to do something bespoke, it will let you if you understand enough code.
The following is the list of time intervals that D3 will consider when setting automatic ticks on
a time based axis;
Just for giggles have a think about what value of ticks you will need to increase to until you get
D3 to show more than five ticks.
Hopefully you wont sneak a glance at the following graph before you come up with the right
answer.
Yikes! The answer is 10! And then when it does, the number of ticks is so great that they jumble
all over each other. Not looking to good there. However, you could rotate the text (or perhaps
slant it) and it could still fit in (that must be the topic of a future how-to). You could also make the
graph longer if you wanted, but of course that is probably going to create other layout problems.
Try to think about your data and presentation as a single entity.
The code that formats the y axis is pretty similar;
https://github.com/mbostock/d3/wiki/Time-Scales
36
We can change the orientation to "right" if we want, but it wont be winning any beauty prizes.
37
Im aware that the statement above may be somewhat ambiguous. You would be justified in
thinking that we already had the data stored and ready to go. But thats not strictly correct.
What we have is data in a raw format, we have added pieces of code that will allow the data to
be adjusted for scale and range to fit in the area that we want to draw, but we havent actually
taken our raw data and adjusted it for our desired coordinates. Thats what the code above does.
The main function that gets used here is the d3.svg.line() function. This function uses
accessor functions to store the appropriate information in the right area and in the case above
they use the x and y accessors (that would be the bits that are .x and .y). The d3.svg.line()
function is called a path generator and this is an indication that it can carry out some pretty
clever things on its own accord. But in essence its job is to assign a set of coordinates in a form
that can be used to draw a line.
Each time this line function is called on, it will go through the data and will assign coordinates
to date and close pairs using the x and y functions that we set up earlier (which of course
are responsible for scaling and setting the correct range / domain).
Of course, it doesnt get the data all by itself, we still need to actually call the valueline function
with data as the source to act on. But never fear, thats coming up soon.
38
We also add an element g that is referenced to the top left corner of the actual graph area on
the canvas. g is actually a grouping element in the sense that it is normally used for grouping
together several related elements. So in this case those grouped elements will have a common
reference.
(the image above is definitely not to scale, but I hope you get the general idea)
Interesting things to note about the code. The .attr(stuff in here) parts are attributes of
the appended elements they are part of.
For instance;
39
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
tells us that the svg element has a width of width + margin.left + margin.right and the height
of height + margin.top + margin.bottom.
Likewise
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"\
);
tells us that the element g has been transformed by moving(translating) to the point margin.left,
margin.top. Or to the top left of the graph space proper. This way when we tell something to be
drawn on our canvas, we can use the reference point g to make sure everything is in the right
place.
This area occurs in the part of the code that has the data loaded and ready for action.
The svg.append("path") portion adds a new path element . A path element represents a shape
that can be manipulated in lots of different ways (see more here: http://www.w3.org/TR/SVG/paths.html).
In this case it inherits the path styles from the CSS section and on the following line (.attr("d",
valueline(data));) we add the attribute d.
This is an attributer that stands for path data and sure enough the valueline(data) portion of
the script passes the valueline array (with its x and y coordinates) to the path element. This then
creates a svg element which is a path going from one set of valueline coordinates to another.
Then we get to draw in the axes;
http://www.w3.org/TR/SVG/paths.html
40
svg.append("g")
// Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
We have covered the formatting of the axis components earlier. So this part is actually just about
getting those components drawn onto our canvas.
So both axes start by being appended to the g group. Then each has its own classes applied for
styling via CSS. If you recall from earlier, they look a little like this;
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
Feel free to mess about with these to change the appearance of your axes.
On the x axis, we have a transform statement (.attr("transform", "translate(0," + height
+ ")")). If you recall, our point of origin for drawing is in the top left hand corner. Therefore
if we want our x axis to be on the bottom of the graph, we need to move (transform) it to the
bottom by a set amount. The set amount in this case is the height of the graph proper (height).
So, for the point of demonstration we will remove the transform line and see what happens;
41
Wrap Up
Well thats it. In theory, you should now be a complete D3 ninja.
OK, perhaps a slight exaggeration. In fact there is a strong possibility that the information I
have laid out here is at best borderline useful and at worst laden with evil practices and gross
inaccuracies.
But look on the bright side. Irrespective of the nastiness of the way that any of it was
accomplished or the inelegance of the code, if the picture drawn on the screen is pretty, you
can walk away with a smile. :-)
This section concludes a very basic description of one type of a graphic that can be built with
D3. We will look at adding value to it in subsequent chapters.
Ive said it before and Ill say it again. This is not a how-to for learning D3. This is how I have
managed to muddle through in a bumbling way to try and achieve what I wanted to do. If
some small part of it helps you. All good. Those with a smattering of knowledge of any of the
topics I have butchered above (or below) are fully justified in feeling a large degree of righteous
indignation. To those I say, please feel free to amend where practical and possible, but please
bear in mind this was written from the point of view of someone with no experience in the topic
and therefore try to keep any instructions at a level where a new entrant can step in.
We will put it in between the blocks of script that add the x axis and the y axis.
43
svg.append("g")
// Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//
svg.append("g")
// Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
Well, it certainly did what it was asked to do. Theres a Date label as advertised! (Yes, I know
its not pretty.) Lets describe the code and then work out why theres a better way to do it.
svg.append("text")
// text label for the x axis
.attr("x", 265 )
.attr("y", 240 )
.style("text-anchor", "middle")
.text("Date");
The first line appends a text element to our canvas. There is a lot more to learn about text
elements at the home of the World Wide Web Consortium (W3C). The next two lines (
.attr("x", 265 ) and .attr("y", 240 ) ) set the attributes for the x and y coordinates
to position the text on the canvas.
The second last line (.style("text-anchor", "middle")) ensures that the text style is such
that the text is centre aligned and therefore remains nicely centred on the x,y coordinates that
we send it to.
The final line (.text("Date");) adds the actual text that we are going to place.
That seems really simple and effective and it is. However, the bad part about it is that we have
hard coded the location for the date into the code. This means if we change any of the physical
http://www.w3.org/TR/SVG/text.html#TextElement
44
aspects of the graph, we will end up having to re-calculate and edit our code. And we dont want
to do that.
Heres an example. If I decide that I would prefer to increase the height of the graph by editing
the line here;
height = 270 - margin.top - margin.bottom;
EVERYTHING about the graph has adjusted itself, except our nasty, hard coded Date label. This
is far from ideal and can be easily fixed by using the variables that we set up ever so carefully
earlier.
So, instead of;
.attr("x", 265 )
.attr("y", 240 )
So with this code we tell the script that the Date label will always be halfway across the width of
the graph (no matter how wide it is) and at the bottom of the graph with respect to its height and
the bottom margin (remember it uses a coordinates system that increases from the top down).
The end result of using variables is that if I go to an extreme of changing the height and width
of my graph to;
45
This uses the "transform" attribute to move (translate) the point to place the Date label to
exactly the same spot that weve been using for the other two examples (using variables of
course).
46
So, thats the x axis label. Time to do the y axis. The code were going to use looks like this;
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Value");
For the sake of neatness we will put the piece of code in a nice logical spot and this would be
following the block of code that added the y axis (but before the closing curly bracket)
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//
});
47
There we go, a label for the y axis that is nicely centred and (gasp!) rotated by 90 degrees! Woah,
does the leetness never end! (No. No it does not.)
So, how do we get to this incredible result?
The first thing we do is the same as for the x axis and append a text element to our canvas
(svg.append("text")).
Then things get interesting.
.attr("transform", "rotate(-90)")
Because that line rotates everything by -90 degrees. While its obvious that the text label Value
has been rotated by -90 degrees (from the picture), the following lines of code show that we also
rotated our reference point (which can be a little confusing).
.attr("y", 0 margin.left)
.attr("x",0 - (height / 2))
Heres our starting position, with x,y in the 0,0 coordinate of the graph drawing area surrounded
by the margins.
When we apply a -90 degrees transform we get the equivalent of this;
48
Here the 0,0 coordinate has been shifted by -90 degrees and the x,y designations are flipped so
that we now need to tell the script that were moving a y coordinate when we would have
otherwise been moving x.
Hence, when the script runs
.attr("y", 0 margin.left)
we can see that this is moving the x position to the left from the new 0 coordinate by the
margin.left value.
Likewise when the script runs
.attr("x",0 - (height / 2))
this is actually moving the y position from the new 0 coordinate halfway up the height of the
graph area.
I will be the first to admit that this does seem a little confusing. But heres the good
part. You really dont need to understand it completely. Simply do what I did when I
saw the code. Play with it a bit till you get the result you were looking for. If that means
putting in some hard coded numbers and incrementing them to see which way is the
new up. Good! Once you work it out, then work out how to get the right variable
expression in there and youre set.
In the worst case scenario, simply use the code blocks as shown here and leave well
enough alone :-).
Right, were not quite done yet. The following line has the effect of shifting the text slightly to
the right.
49
.attr("dy", "1em")
Firstly the reason we do this is that our previous translation of coordinates means that when we
place our text label it sits exactly on the line of 0 margin.left. But in this case that takes the text
to the other side of the line, so it actually sits just outside the boundary of the overall canvas.
The "dy" attribute is another coordinate adjustment move, but this time a relative adjustment
and the 1em is a unit of measure that equals exactly one unit of the currently specified text
point size. So what ends up happening is that the Value label gets shifted to the right by exactly
the height of the text, which neatly places it exactly on the edge of the canvas.
The two final lines of this part of the script are the same as for the x axis. They make sure the
reference point is aligned to the centre of the text (.style("text-anchor", "middle")) and then
it prints the text (.text("Value");). There, that wasnt too painful.
http://en.wikipedia.org/wiki/Em_(typography)
50
A nice logical place to put the block of code would be towards the end of the JavaScript. In fact
I would put it as the last element we add. So here;
svg.append("g")
// Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
// PUT THE NEW CODE HERE!
});
Now since the vast majority of the code for this block is a regurgitation of the axis labels code, I
dont want to revisit that and bloat up this document even more, so I will direct you back to that
section if you need to refresh yourself on any particular line. But.. There are a couple of new
ones in there which could benefit from a little explanation.
Both of them are style descriptors and as such their job is to apply a very specific style to this
element.
51
.style("font-size", "16px")
.style("text-decoration", "underline")
What they do is pretty self explanatory. Make the text a specific size and underline it. But what
is perhaps slightly more interesting is that we have this declaration in the JavaScript code and
not in the CSS portion of the file.
Strictly speaking, this is the sort of thing that would be placed in the <style> section
of the HTML code, but in this case, since it is only going to be used once, we shouldnt
feel too bad putting it here.
52
The resulting variation of the graph shows a fair amount of extremes and you could be forgiven
for thinking that if this represented a smoothly flowing analog system of some kind then some
of those sharp peaks and troughs would not be a true representation of how the system or figures
varied.
So how should it look? Ahh The $64,000 question. I dont know :-). You will have a better idea
since you are the person who will know your data best. However, what I do know is that D3 has
some tricks up its sleeve to help.
We can easily change what we see above into;
53
How about that? And the massive amount of code required to carry out what must be a
ridiculously difficult set of calculations?
.interpolate("basis")
Now, that is slightly unfair because thats the code that YOU need to put in your script,
but Mike Bostock probably had to do the mental equivalent of walking across hot coals
to get it to work so nicely.
So is that it? Nooooo.. Theres more! This is one form of interpolation effect that can be
applied to your data, but there is a range and depending on your data you can select the one that
is appropriate.
Heres the list of available options and for more about them head on over to the D3 wiki and
look for line.interpolate.
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-line_interpolate
54
cardinal - a Cardinal spline, with control point duplication on the ends. It looks slightly
more jagged than basis.
cardinal-open - an open Cardinal spline; may not intersect the start or end, but will
intersect other control points. So kind of shorter than cardinal.
cardinal-closed - a closed Cardinal spline, looped back on itself.
monotone - cubic interpolation that makes the graph only slightly smoother.
Because in the course of writing this I took an opportunity to play with each of them, I was
pleasantly surprised to see some of the effects and it seems like a shame to deprive the reader
of the same joy :-). So at the risk of deforesting the planet (so I hope you are reading this in
electronic format) here is each of the above interpolation types applied to the same data.
This is also an opportunity to add some reader feedback awesomeness. Many thanks to enjalot
for the great suggestion to plot the points of the data as separate circles on the graphs. Since the
process of interpolation has the effect of interpreting the trends of the data to the extent that
in some cases, the lines dont intersect the actual data much at all.
Each of the following shows the smoothing curve and the data that is used to plot the graph.
55
56
57
Just in case youre in the mood for another example, here are voronoi tessellations drawn with
various d3 line interpolators (the original interactive version by shawnbot can be found here).
http://bl.ocks.org/shawnbot/5970227
58
First a version using the linear interpolation when each of the points is joined faithfully with a
straight line.
Now a version where the polygons are formed with the basis-closed interpolator (note how the
lines dont go through the points that describe the bounds of the polygons/blobs).
59
And lastly, using the cardinal-closed interpolator, while the line travels through each point in
the polygon, they overshoot in an effort to maintain a nice curve and the resulting polygon/blobs
overlap.
So, over to you to decide which format of interpolation is going to suit your data best:-).
60
Like pretty much everything in this document, the clever parts of this are not my work.
Ive simply used other peoples cleverness to solve my problems. In this case I think
the source of this solution came from the good work of Justin Palmer in his excellent
description of the design of a line graph here. However, in retrospect when Ive looked
back, Im not sure if I got this right (as I did this quite a while ago when I was less
fastidious about noting my sources). In any case, Justins work is excellent and I heartily
recommend it, and here is my implementation of what I think is his work :-)
61
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
Just add this block of code at the end of the current CSS that is in the simple graph template (just
before the </style> tag).
The CSS here is done in two parts.
The first portion sets the line colour (stroke) and the opacity (transparency) of the lines.
stroke: lightgrey;
opacity: 0.7;
The colour is pretty standard, but in using the opacity style we give ourselves the opportunity
to use a good shade of colour (if grey actually is a colour) and to juggle the degree to which it
stands out a little better.
The second part is the stroke width.
stroke-width: 0;
Now it might seem a little weird to be setting the stroke width to zero, but if you dont (and we
remove the style) this is what happens;
If you look closely (compare with the previous picture if necessary) the main lines for the axis
have turned thicker. The stroke width style is obviously adding in new (thicker) axis lines and
were not interested in them at the moment. Therefore, if we set the stroke width to zero, we get
rid of the problem.
62
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
}
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
}
Each function will carry out its configuration when called from the later part of the script (the
drawing part).
A good spot to place the code is just before we load the data with the d3.tsv
//
<== Put the functions here!
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});ticks(5)
}
Both functions are almost identical. They give the function a name (make_x_axis and make_y_axis) which will be used later when the piece of code that draws the lines calls out to them.
Both functions also show which parameters will be fed back to the drawing process when called.
Both make sure they use the d3.svg.axis function and then they set individual attributes which
make sense.
They make sure theyve got the right axis (.scale(x) and .scale(y)). They set the orientation of
the axes to match the incumbent axes (.orient("bottom") and .orient("left")). And they set
the number of ticks to match the number of ticks in the main axis (.ticks(5) and .ticks(5)).
You have the opportunity here to do something slightly different if you want. For instance, think
back to when we were setting up the axis for the basic graph and we messed about, seeing how
many ticks we could get to appear. If we increase the number of ticks that appear in the grid (lets
say to .ticks(30) and .ticks(10))) we get the following;
63
So the grid lines can now show divisions of 50 on the y axis and per day on the x axis :-)
The first two lines of both the x and y axis grid lines code above should be pretty familiar by now.
The first one appends the element to be drawn to the group g. the second line (.attr("class",
"grid")) makes sure that the style information set out in the CSS is applied.
The x axis grid lines portion makes a slight deviation from conformity here to adjust its
positioning to take into account the coordinates system .attr("transform", "translate(0,"
+ height + ")").
Then both portions call their respective make axis functions (.call(make_x_axis() and .call(make_y_axis()).
Now comes the really interesting bit.
What you will see if you go to the D3 API wiki is that for the .tickSize function, the following
is the format.
https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-tickSize
64
That tells us that you get to specify the size of the ticks on the axes, by the major ticks, the minor
ticks and the end ticks (that is to say the lines on the very end of the graph which, in the case of
the example we are looking at, arent there!).
So in our example we are setting our major ticks to a length that corresponds to the full height
or width of the graph. Which of course means that they extend across the graph and have the
appearance of grid lines! What a neat trick.
Something I havent done before is to see what would happen if I included the tick lines for the
minor and end ticks. So here we go :-)
Darn! Disappointment. We can see a minor tick line for the y axis, but nothing for the x axis and
nothing on the ends. Clearly I will have to run some experiments to see whats going on there
(later).
The last thing that is included in the code to draw the grid lines is the instruction to suppress
printing any label for the ticks;
.tickFormat("")
After all, that would become a bit confusing to have two sets of labels. Even if one was on top
of the other. They do tend to become obvious if that occurs (they kind of bulk out a bit like bold
text).
And thats it. Grid lines!
65
66
Well I suppose you can have too much of a good thing. With great power comes great
responsibility. Use your dash skills wisely and only for good.
67
68
that there is no line surrounding the filled area, it will assume that there is one and add it in like
this.
So what has happened here is that the area element has inherited the line property from the path
element and surrounding the area is a 2px wide steelblue line. Not too pretty. Lets not go there.
area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
I have placed it in between the axis variable definitions and the line definitions here;
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
<==== Put the new code here!
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
You will notice it looks INCREDIBLY similar to the valueline function definition.
Thats because; while the line definition describes drawing a line that connects a set
of coordinates, I imagine the area definition describes drawing two lines that share the
same x coordinates, but simultaneously draws two y coordinates, y0 and y1. Then when
its finished drawing the resultant shape, it fills it with the colour of your choosing.
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-area
69
So the only changes to the code are the addition of the y0 line and the renaming of the y line y1.
Heres a picture that might help explain;
As should be apparent, the top line (y1) follows the valueline line and the bottom line is at the
constant height value. Everything in between these lines is what gets filled. The function in
this section describes the area.
We should place this block directly after the domain functions but before the drawing of the
valueline path;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
//
<== Area drawing code here!
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
This is actually a pretty good idea to put it there since the various bits and pieces that are drawn
in the graph are done so one after the other. This means that the filled area comes first, then the
valueline is layered on top and then the axes come last. This is a pretty good sequence since if
there are areas where two or more elements overlap, it might cause the graph to look wrong.
For instance, here is the graph drawn with the area added last.
70
You should be able to notice that part of the valueline line has been obscured and the line for the
y axis where it coincides with the area is obscured also.
Looking at the code we are adding here, the first line appends a path element (svg.append("path"))
much like the script that draws the line.
The second line (.datum(data)) declares the data we will be utilising for describing the area and
the third line (.attr("class", "area")) makes sure that the style we apply to it is as defined
in the CSS section (under area).
The final line (.attr("d", area);) declares d as the attributer for path data and calls the area
function to do the drawing.
And thats it!
In this instance, you could fill the lower area as has been demonstrated here, and with a small
change you can fill another area with a solid colour above another line.
How is this incredible feat achieved?
Well, remember the code that defined the area?
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
71
All we have to do is tell it that instead of setting the y0 constant value to the height of the graph
(remember, this is the bottom of the graph) we will set it to the constant value that is at the top
of the graph. In other words zero (0).
.y0(0)
Thats it.
Now, Im not going to go over the process of drawing two lines and filling each in different
directions to demonstrate the example I described, but this provides a germ of an idea that you
might be able to flesh out :-)
72
Is this evil?
Now, Ill be the first to say that the principle of overlaying text on a graph is probably
not best practice, but sometimes youve got to do what youve got to do. Besides.
Sometimes its a valid idea. If I remember rightly, the first time I came across this
idea, it was being used to highlight text when positioned on bars of a bar graph. So its
not always an evil practice :-).
Anyway, what well do is leave the fill in place and place the title back on the graph, but position
the title so that it lays on top of the fill like so;
The additional code for the title is the following and appears just after the drawing of the axes.
svg.append("text")
.attr("x", (width / 2))
.attr("y", 25 )
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Value vs Date Graph");
(the only change from the previous title example is the y attribute which has been hard coded
to 25 to place it inconveniently on the filled area.)
So, what we want to end up with is something like the following
73
In my humble opinion, its just enough to make the text acceptable :-).
The method that Ill describe to carry this out is designed so that the drop shadow effect can be
applied to any text elements in the graph, not the isolated example that we will use here. In order
to implement this marvel of utility we will need to make changes in two areas. One in the CSS
where we will define a style for white shadowy backgrounds and the second to draw it.
The first line designates that the style applies to text with a shadow label. The stroke is set to
white. the width of the line is set to 2.5px and it is made to be slightly see-through. So by setting
the line that surrounds the text to be thick, white and see-through gives it a slightly cloudy
effect. If we remove the black text from over the top we get a slightly better look;
Of course if you want to have a play with any of these settings, you should have a go and see
what works best for your graph.
svg.append("text")
.attr("x", (width / 2))
.attr("y", 25 )
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.attr("class", "shadow")
.text("Value vs Date Graph");
74
Thats because its identical to the piece of code that was used to draw the title except for the one
line that is indicated above. The reason that its identical is that what we are doing is placing a
white shadow on the graph and then the text on top of it, if it deviated by a significant amount
it will just look silly. Of course a slight amount could look effective, in which case adjust the x
or y attributes.
One of the things I pointed out in the previous paragraph was extremely important. Thats the
bit that tells you that we needed to place the shadow before we placed the black text. For the
same reason that we placed the area fill on first in the area fill example, If black text goes on
before the shadow, it will look pretty silly. So place this block of code just before the block that
draws the title.
So the line that has been added in is the one that tells D3 that the text that is being drawn
will have the white cloudy effect. And at the risk of repeating myself, if you have several text
elements that could benefit from this effect, once you have the CSS code in place, all you need
to do is duplicate the block that adds the text and add in that single line and voila!
75
So, how are we going to do this? I think that the best way will be to make the executive decision
that we have suddenly come across more data and that it is also in our data.tsv file. In fact it
looks a little like this (apologies in advance for the big ugly block of data);
date
1-May-12
30-Apr-12
27-Apr-12
26-Apr-12
25-Apr-12
24-Apr-12
23-Apr-12
20-Apr-12
19-Apr-12
18-Apr-12
17-Apr-12
16-Apr-12
13-Apr-12
12-Apr-12
11-Apr-12
10-Apr-12
9-Apr-12
5-Apr-12
4-Apr-12
3-Apr-12
2-Apr-12
30-Mar-12
29-Mar-12
28-Mar-12
27-Mar-12
26-Mar-12
close
58.13
53.98
67.00
89.70
99.00
130.28
166.70
234.98
345.44
443.34
543.70
580.13
605.23
622.77
626.20
628.44
636.23
633.68
624.31
629.32
618.63
599.55
609.86
617.62
614.48
606.98
open
34.12
45.56
67.89
78.54
89.23
99.23
101.34
122.34
134.56
160.45
180.34
210.23
223.45
201.56
212.67
310.45
350.45
410.23
430.56
460.34
510.34
534.23
578.23
590.12
560.34
580.12
Three columns, date open and close. The first two are exactly what we have been dealing with
76
all along and the last (open) is our new made up data. Each column is separated by a tab (hence
.tsv (Tab Separated Values)), which is the format were currently using to import data.
We should save this as a new file so we dont mess up our previous data, so lets call it data2.tsv.
We will be using our simple graph template to start with, so the immediate consequence of this
is that we need to edit the line that was looking for data.tsv to reflect the new name.
d3.tsv("data/data2.tsv", function(error, data) {
So when you browse to our new graphs html file, we dont see any changes. It still happily loads
the new data, but because it hasnt been told to do anything with it, nothing new happens.
What we need to do now it to essentially duplicate the code blocks that drew the first line for
the second line.
The good news is that in the simplest way possible thats just two code blocks. The first sets up
the function that defines the new line;
var valueline2 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.open); });
You should notice that this block is identical to the block that sets up the function for the first
line, except this one is called (imaginatively) valueline2. We should put it directly after the block
that sets up the function for valueline.
The second block draws our new line;
svg.append("path")
// Add the valueline2 path.
.attr("class", "line")
.attr("d", valueline2(data));
Again, this is identical to the block that draws the first line, except this one is called valueline2.
We should put it directly after the block that draws valueline.
After those three small changes, check out your new graph;
77
Hey! Two lines! Hmm. Both being the same colour is a bit confusing. Good news. We can
change the colour of the second line by inserting a line that adjusts its stroke (colour) very
simply.
So heres what our new drawing block looks like;
svg.append("path")
// Add the valueline2 path.
.attr("class", "line")
.style("stroke", "red")
.attr("d", valueline2(data));
Wow. Right about now, were thinking ourselves pretty clever. But theres two places where were
not doing things right. We took a simple way, but we took some short cuts that might bite us in
the posterior.
The first mistake we made was not ensuring that our variable "d.open" is being treated as a
number or a string. Were fortunate in this case that it is, but this cant always be assumed. So,
this is an easy fix and we just need to put the following (indicated line) in our code;
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
d.open = +d.open;
// <=== Add this line in!
});
The second and potentially more fatal flaw is that nowhere in our code do we make allowance
for our second set of data (the second lines values) exceeding our first lines values.
That might not sound too normal straight away, but consider this. What if when we made up
our data earlier, some of the new data exceeded our maximum value in our original data? As a
means of demonstration, heres what happens when our second line of data has values higher
than the first lines;
78
So that only considers d.close when establishing the domain. With d.open exceeding our domain,
it just keeps drawing off the graph!
The good news is that Bill has provided a solution for just this problem here;
All you need to replace the y.domain line with is this;
y.domain([0, d3.max(data, function(d) { return Math.max(d.close, d.open); })]);
It does much the same thing, but this time it returns the maximum of d.close and d.open
(whichever is largest). Good work Bill.
If we put that code into the graph with the higher values for our second line we are now presented
with this;
And it doesnt matter which of the two sets of data is largest, the graph will always adjust :-)
You will also have noticed that our y axis has auto adjusted again to cope. Clever eh?
http://stackoverflow.com/questions/12732487/d3-js-dataset-array-w-multiple-y-axis-values
79
To do this we use the transform and translate attribute and find the x position that equates
to the end of the graph plus 3 pixels ((width+3)) (we add in the three pixels to create a small
separation between the end of the line and the label). The y position is far more interesting. We
need to find the position of the last point in our line for the open data. Because the data is in the
form of an indexed array and because the data has the latest date at the start of the array, we
only need to find the point at the 0 position of the array. This is data[0].open. But of course, we
also need to adjust our data for our scale and range, so we transform it using the y function (in
the same way that we do it for the valueline and valueline2 points. So the script to find the
point on the screen in the y direction is y(data[0].open).
If our data was arranged with the last date at the end of our data we would have to find the final
index point and we would use y(data[data.length-1].open)).
Then its just a matter of aligning and justifying our text correctly;
http://www.d3noob.org/2013/01/adding-more-than-one-line-to-graph-in.html
80
.attr("dy", ".35em")
.attr("text-anchor", "start")
We put this block of code after the blocks that add in the axes so that they make sure theyre on
top of anything else we draw.
The only other small change we want to make is to change the right margin for the graph that we
set at the start of our script from 20 to 40 so that there is enough room to add our label without
cutting it off.
After that you have a marvellously labelled multi-line graph!
The full code for this example can be found on github or in the code samples bundled with this
book (dual-line-labelled.html and data2b.csv). A working example can be found on bl.ocks.org.
Now, Id like to pretend that this is perfection, but it isnt. If our lines end too close together, the
labels will interfere with each other, so in the ideal world I would include a bit of fanciness to
prevent that, but for the purposes of this exercise we can consider ourselves happy.
https://gist.github.com/d3noob/8603837
http://bl.ocks.org/d3noob/8603837
81
close
58.13
53.98
67.00
89.70
99.00
130.28
166.70
234.98
345.44
443.34
543.70
580.13
605.23
622.77
626.20
628.44
636.23
633.68
624.31
629.32
618.63
599.55
609.86
617.62
614.48
606.98
open
3.41
4.55
6.78
7.85
8.92
9.92
10.13
12.23
13.45
16.04
18.03
21.02
22.34
20.15
21.26
31.04
35.04
41.02
43.05
46.03
51.03
53.42
57.82
59.01
56.03
58.01
Now this isnt a problem in itself. D3 will still make a reasonable graph of the data, but because
of the difference in range, the detail of the second line will be lost.
82
What Im proposing is that we have a second y axis on the right hand side of the graph that
relates to the red line.
The mechanism used is based on the great examples put forward by Ben Christensen here.
Now Youll need to concentrate a bit since there are quite a few different bits to
change and adapt, but dont despair, theyre all quite logical and make sense.
First things first, there wont be space on the right hand side of our graph to show the extra axis,
so we should make our right hand margin a little larger.
var margin = {top: 30, right: 40, bottom: 30, left: 50},
Then change our yAxis declaration to be specific for y0 and specifically left. And add in a
declaration for the right hand axis;
http://benjchristensen.com/2012/05/02/line-graphs-using-d3-js/
83
//
<==
// <== y0
// <== y1
There are a few different ways for the scaling to work, but well stick with the fancy max method
we used in the dual line example (although technically its not required).
y0.domain([0, d3.max(data, function(d) { return Math.max(d.close); })]);
y1.domain([0, d3.max(data, function(d) { return Math.max(d.open); })]);
Again, heres the y0 and y1 changed and added and the maximums for d.close and d.open are
separated out). The final piece of the puzzle is to draw the new axis, but we also want to make
a slight change to the original y axis. Since we have two lines and two axes, we need to know
which belongs to which, so we can colour code the text in the axes to match the lines;
svg.append("g")
.attr("class", "y axis")
.style("fill", "steelblue")
.call(yAxisLeft);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + " ,0)")
.style("fill", "red")
.call(yAxisRight);
In the above code you can see where we have added in a style change for the yAxisLeft to make
it steelblue and a complementary change in the new section for yAxisRight to make that text
red.
The yAxisRight section obviously needs to be added in, but the only significant difference is the
transform / translate attribute that moves the axis to the right hand side of the graph.
84
Two lines with full range of the domain and two axes
Now, lets not kid ourselves that its a thing of beauty, but we should console our aesthetic
concerns with the warm glow of understanding how the function works :-).
85
We postulated at the time that an answer to the problem might be to rotate the text to provide
more space. Well, its about time we solved that problem.
The answer I found most usable was provided by Aaron Ward on Google Groups.
Starting out with our simple graph example, we should increase the number of ticks on the x
axis to 10 to highlight the problem in the previous image.
The first substantive change would be a little housekeeping. Because we are going to be rotating
the text at the bottom of the graph, we are going to need some extra space to fit in our labels. So
we should change our bottom margin appropriately.
var margin = {top: 30, right: 40, bottom: 50, left: 50},
https://groups.google.com/forum/#!msg/d3-js/CRlW0ISbOy4/1sgrE5uS5ysJ
86
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
Its pretty standard until the .call(xAxis) portion of the code. Here we remove the semicolon
that was there so that the block continues with its function.
Then we select all the text elements that comprise the x axis with the .selectAll("text"). From
this point onwards, we are operating on the text elements associated with the x axis. In effect;
the following 4 actions are applied to the text labels.
The .style("text-anchor", "end") line ensures that the text label has the end of the label
attached to the axis tick. This has the effect of making sure that the text rotates about the end
of the date. This makes sure that the text all ends up at a uniform distance from the axis ticks.
The dx and dy attribute lines move the end of the text just far enough away from the axis tick so
that they dont crowd it and not too far away so that it appears disassociated. This took a little bit
of fiddling to look right and you will notice that Ive used the em units to get an adjustment
if the size of the font differs.
The final action is kind of the money shot.
The transform attribute applies itself to each text label and rotates each line by -65 degrees. I
selected -65 degrees just because it looked OK. There was no deeper reason.
The end result then looks like the following;
This was a surprisingly difficult problem to find a solution to that I could easily understand (well
done Aaron). That makes me think that there are some far deeper mysteries to it that I dont fully
appreciate that could trip this solution up. But in lieu of that, enjoy!
87
What the tickFormat allows is the setting of formatting for the tick labels. The d3.time.format
portion of the code is specifying the exact format of those ticks. This formatting is described
using the same arguments that were explained in the earlier section on formatting date time
values. That means that the examples we see here (%Y-%m-%d) should display the year as a four
digit number then a hyphen then the month as a two digit number, then another hyphen, then a
two digit number corresponding to the day.
Lets take a look at the result;
88
There we go! You should be able to see this file in the downloads section on d3noob.org with the
general examples as formatted-date-time-axis-labels.html.
So how about we try something a little out of the ordinary (extreme)?
How about the full weekday name (%A), the day (%d), the full month name (%B) and the year (%Y)
as a four digit number?
.tickFormat(d3.time.format("%A %d %B %Y"));
We will also need some extra space for the bottom margin, so how about 140?
var margin = {top: 30, right: 40, bottom: 140, left: 50},
and.
89
Adding a Button
Its all well and good animating your data, but if you dont know when its supposed to happen
or what should happen, its a little difficult to evaluate how successful youve been.
To make life easy, were going to take some of the mystery out of the equation (dont worry, well
put it back later) and add a button to our graph that will give you control over when your graph
should update its data. When complete it should look like this;
http://mbostock.github.com/d3/tutorial/protovis.html
90
To add a button, we will take our simple-graph.html example and just after the <body> tag we
add the following code;
<div id="option">
<input name="updateButton"
type="button"
value="Update"
onclick="updateData()"
/>
</div>
The HTML <div> element (or HTML Document Division Element) is used to assign a division
or section in an HTML document. We use it here as its good practice to keep sections of your
HTML document distinct so that its easier to perform operations them at a later date.
In this case we have given the div the identifier option so that we can refer to it later if we
need to (embarrassingly, we wont be referring to it at all, but its good practice none the less).
The following line adds our button using the HTML <input> tag. The <input> tag has a wide
range of attributes (options) for allowing user input. Check out the links to w3schools and
Mozilla for a whole lot of reading.
In our <input> line we have four different attributes;
name
type
value
onclick
http://www.w3schools.com/tags/tag_input.asp
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
91
Each of these attributes modifies the <input> function in some way so that our button does what
we want it to do.
name:
This is the name of the control (in this case a button) so that we can reference it in other parts
of our HTML script.
type:
Probably the most important attribute for a button, this declares that our type of input will be
a button! There are heaps of other options for type which would form a significant section in
itself.
value:
For a button input type, this is the starting value for our button and forms the label that our
button will have.
onclick:
This is not an attribute that is specific to the <input> function, but it allows the browser to
capture a mouse clicking event when it occurs and in our case we tell it to run the updateData()
function (which well be seeing more of soon).
92
function updateData() {
// Get the data again
d3.tsv("data/data-alt.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line")
// change the line
.duration(750)
.attr("d", valueline(data));
svg.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
});
}
Repeatability
Its worth noting that while our updateData function only appears to work the once
when you first click the button, in fact every time the button is pushed the updateData
function is carried out. Its just that since the data doesnt change after the first click,
you never see any change.
93
Then we get our new data with the block that starts with d3.tsv("data/data-alt.tsv". This is
a replica of the block in the main part of the code with one glaring exception. It is getting the data
from our new file called data-alt.tsv. However, one thing its doing that bears explanation is
that its loading data into an array that weve already used to generate our line. At a point not
too far from here (probably the next page) were going to replace the data that made up our line
on the page with the new data thats just been loaded.
We then set the scale and the range again using the x.domain and y.domain lines. We do this
because its more than possible that our data has exceeded or shrunk with respect to our original
domains so we recalculate them using our new data. The consequence of not doing this would
be a graph that could exceed its available space or be cramped up.
Then we assign the variable svg to be our selection of the "body" div (which means the following
actions will only be carried out on objects within the "body" div.
Selection Study.
Selections are a very important topic and if reading Google Groups and Stack Overflow
are anything to go by they are also a much misunderstood feature of D3. I wont
claim to be in any better position to describe them, but I would direct readers to a
description of nested selections by Mike Bostock (http://bost.ocks.org/mike/nest/) and
a video tutorial by Ian Johnson (http://blog.visual.ly/using-selections-in-d3-to-makedata-driven-visualizations/).
The other part of that line is the transition command (.transition()). This command goes to
the heart of animating dynamic data and visualizations and is a real treasure.
Transition Training
I will just be brushing the surface of the subject of transitions in d3.js, and
I will certainly not do the topic the justice it deserves for in depth animations. I heartily recommend that you take an opportunity to read Mike
Bostocks Path Transitions (http://bost.ocks.org/mike/path/), bar chart tutorial
(http://mbostock.github.com/d3/tutorial/bar-2.html) and Jerome Cukiers Creating
Animations and Transitions with D3 (http://blog.visual.ly/creating-animations-andtransitions-with-d3-js/). Of course, one of the main resources for information on
transitions is also the D3 wiki (https://github.com/mbostock/d3/wiki/Transitions).
As the name suggests, a transition is a method for moving from one state to another. In its
simplest form for a d3.js visualisation, it could mean moving an object from one place to another,
or changing an objects properties such as opacity or colour. In our case, we will take our data
which is in the form of a line, and change some of that data. And when we change the data we
will get d3 to manage the change via a transition. At the same time (because were immensely
clever) we will also make sure we change the axes if they need it.
So in short, were going to change this
94
into this
Updated data
Obviously the line values have changed, and both axes have changed as well. And using a
properly managed transition, it will all occur in a smooth ballet :-).
So, looking at the short block that manages the line transition;
svg.select(".line")
// change the line
.duration(750)
.attr("d", valueline(data));
We select the ".line" object and since weve already told the script that svg is all about the
transition (var svg = d3.select("body").transition();) the attributes that follow specify
how the transition for the line will proceed. In this case, the code describes the length of
time that the transition will take as 750 milliseconds (.duration(750)) and uses the new data
as transcribed by the valueline variable from the original part of the script (.attr("d",
valueline(data));).
95
The same is true for both of the subsequent portions of the code that change the x and y axes.
Weve set both to a transition time of 750 milliseconds, although feel free to change those values
(make each one different for an interesting effect).
Other attributes for the transition that we could have introduced would be a delay (.delay(500),
perhaps to stagger the movements) and more interestingly an easing attribute (.ease(type[,
arguments])) which will have the effect of changing how the movement of a transition appears
(kind of like a fast-slow-fast vs linear, but with lots of variations).
But for us well survive with the defaults.
In theory, youve added in your new data file (data-alt.tsv) and made the two changes to the
simple graph file (the HTML block for the button and the JavaScript one for the updateData
function). The result has been a new beginning in your wonderful d3 journey!
I have loaded the file for this into the d3noob downloads page with the general example files as
data-load-button.html.
96
Now, we have two references in our JavaScript where we load our data. One loads data.tsv
initially, then when the button was pushed, we loaded data-alt.tsv. Were going to retain that
arrangement for the moment, because we want to make sure we can see something happening,
but ultimately, we would just have them referencing a single file.
So, the magic piece of script that will do your updating is as follows;
var inter = setInterval(function() {
updateData();
}, 5000);
And we should put that just above the function updateData() { line in our code.
The key to this piece of code is the setInterval function which will execute specified code (in
this case its updateData(); which will go and read in our new information) over and over again
at a set interval (in this case 5000 milliseconds (}, 5000);)).
I honestly wish it was harder, but sadly its that simple. You now have in your possession the
ability to make your visualizations do stuff on a regular basis, all by themselves!
How to test?
Well, just load up your new file (Ive called the one thats in the d3noob downloads page with
the general example files data-load-automatic.html). After an interval of 5 seconds, you should
see the graph change all by itself. How cool is that?
97
The Framework
To be able to demonstrate how these three related aspects of drawing objects work we will have
to use a small, simple script to draw them in your web browser.
We will just take a moment to explain the script that draws a circle.
Heres the contents of the file in its entirety. I have imaginatively called it circle.html.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!-- load the d3.js library -->
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
var holder = d3.select("body")
.append("svg")
.attr("width", 449)
.attr("height", 249);
//
//
//
//
// draw a circle
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50);
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
99
</script>
</body>
Please feel free to jump ahead slightly if you understand how a HTML file with JavaScript goes
together :-).
The HTML part of the file can be thought of as a wrapper for the JavaScript that will draw our
circle. These are the HTML parts here
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!-- load the d3.js library -->
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
</script>
</body>
This portion of the file is built using HTML tags. These will set up the environment for the
Javascript.
The tags tell the web browser what sort of language is being used and the type of characters used
to write the code
<!DOCTYPE html>
<meta charset="utf-8">
100
<script>
Our d3.js code will go here.
</script>
We even load an external file that contains JavaScript that will help run our code.
<!-- load the d3.js library -->
<script type="text/javascript" src="d3/d3.v3.js"></script>
Yes, thats the line that loads d3.js. Once its loaded we can use the instructions that it makes
available to make other JavaScript code (in this case ours) work.
Then we have the JavaScript code that allows us to use the functions made possible by d3.js.
var holder = d3.select("body")
.append("svg")
.attr("width", 449)
.attr("height", 249);
//
//
//
//
// draw a circle
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50);
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
Ive broken the code into two separate portions to provide some clarity to their function. We
could make it one block, but that wouldnt necessarily make it easier to understand.
Firstly we add a holder for our graphics on the web page. Ive named it holder but we could
just as easily named it anything we wanted.
var holder = d3.select("body")
.append("svg")
.attr("width", 449)
.attr("height", 249);
//
//
//
//
The first thing we do when declaring our holder is to select the body element of our web page
(Remember those <body> tags in the HTML part earlier?).
Then we append a Scalable Vector Graphic (SVG) object to the body and we make it 449 pixels
wide and 249 pixels high.
The width and height are attributes of the SVG object. That is to say they describe a property
of the object.
101
Believe it or not, I have made the container size unusual (not nice round numbers like
450 x 250) for a good reason. Later I will introduce a grid to our diagram so we can see
where everything is laid out and this size makes the grid look better.
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
The first line appends a new element (a circle) to our SVG holder.
If you like, you can think of having the holder declaration in front of the
.append("circle") as being a nice short-hand way of writing the code. We could
have had a much longer line that selected the body, appended the svg element and
then appended our circle in one line, but its actually a far better scheme for building
multiple objects to break the sequences up which will allow us to manipulate groupings
of objects in future code.
The second and third lines declare the attribute of our circle that specify where the centre of the
circle is. In this case its at the x/y position 200/100 (cx/cy).
The last line adds the radius attribute r. Here it is set to 50 pixels.
The three attributes cx, cy and r are all required when drawing a circle. There are other attributes
we can put in there (and when we look at some of the upcoming elements, you should get a feel
for them), but these are the minimum.
The purpose of describing this block of code that draws a circle isnt to show you how to draw
a circle. This has only been a way of showing you how the code in the following sections is laid
out and how it works. The elements we are going to generate can be drawn with exactly the
same file but with just the section that adds the circle altered.
For example if you were to change this block of code;
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50);
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
102
holder.append("rect")
.attr("x", 150)
.attr("y", 50)
.attr("width", 100)
.attr("height", 100);
//
//
//
//
//
attach a circle
x position of the
y position of the
set the rectangle
set the rectangle
top-left corner
top-left corner
width
height
Circle
Because it will help a great deal to have a common frame of reference, Im going to display the
elements on a grid that looks a little like this;
The grid wont form part of the code that gets explained, but I will take the time out to
describe how its generated in another section, because its quite cool in its own way
:-).
With the grid in place its far easier to see that the centre of our circle is indeed at the coordinates
x = 200, y = 100 and that the radius is 50.
The circle is still somewhat plain, but bear with me because as we start to explore what we can
do with styles and attributes we can add some variation to our elements.
103
Fancier Circle
With that explanation behind us we should begin our odyssey into the world of d3 elements.
Elements
We will begin by describing what we mean when we talk about an element.
There is considerable scope for confusion when talking about elements on a web page. Are we
talking about HTML elements, SVG elements or something different?
In fact we are going to be describing a subset of SVG elements. Specifically those that are
described in the d3.js API reference (since thats why were here right?). These are a collection of
common shapes and objects which include circles, ellipses, rectangles, lines, polylines, polygons,
text and paths.
Text? I hear you say. Doesnt sound like a shape. I suppose it depends on how you think of
it. We can use text in different ways in d3, but for this particular exercise we can regard text as
an SVG element.
http://reference.sitepoint.com/html/page-structure
https://developer.mozilla.org/en-US/docs/Web/SVG/Element
https://github.com/mbostock/d3/wiki/SVG-Shapes#svg-elements
104
Circle
A circle is a simple SVG shape that is described by three required attributes.
cx: The position of the centre of the circle in the x direction (left / right) measured from
the left side of the screen.
cy: The position of the centre of the circle in the y direction (up / down) measured from
the top of the screen.
r: The radius of the circle from the cx, cy position to the perimeter of the circle.
The following is an example of the code section required to draw a circle in conjunction with
the HTML file outlined at the start of this chapter;
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50);
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
Circle
The centre of the circle is at x = 200 and y = 100 and the radius is 50 pixels.
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_circle
105
Ellipse
An ellipse is described by four required attributes;
cx: The position of the centre of the ellipse in the x direction (left / right) measured from
the left side of the screen.
cy: The position of the centre of the ellipse in the y direction (up / down) measured from
the top of the screen.
rx: The radius of the ellipse in the x dimension from the cx, cy position to the perimeter
of the ellipse.
ry: The radius of the ellipse in the y dimension from the cx, cy position to the perimeter
of the ellipse.
The following is an example of the code section required to draw an ellipse in conjunction with
the HTML file outlined at the start of this chapter;
holder.append("ellipse")
.attr("cx", 200)
.attr("cy", 100)
.attr("rx", 100)
.attr("ry", 50);
//
//
//
//
//
attach an ellipse
position the x-centre
position the y-centre
set the x radius
set the y radius
Ellipse
The centre of the ellipse is at x = 200 and y = 100 and the radius is 50 pixels vertically and 100
pixels horizontally.
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_ellipse
106
Rectangle
A rectangle is described by four required attributes and two optional ones;
x: The position on the x axis of the left hand side of the rectangle (required).
y: The position on the y axis of the top of the rectangle (required).
width: the width (in pixels) of the rectangle (required).
height: the height (in pixels) of the rectangle (required).
rx: The radius curve of the corner of the rectangle in the x dimension (optional).
ry: The radius curve of the corner of the rectangle in the y dimension (optional).
The following is an example of the code section required to draw a rectangle (using only the
required attributes) in conjunction with the HTML file outlined at the start of this chapter;
holder.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("height", 100)
.attr("width", 200);
//
//
//
//
//
attach a rectangle
position the left of the rectangle
position the top of the rectangle
set the height
set the width
Rectangle
The top left corner of the rectangle is at 100, 50 and the rectangle is 200 pixels wide and 100 pixels
high.
The following code section includes the optional attributes for the curved corners;
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_rect
107
holder.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("height", 100)
.attr("width", 200)
.attr("rx", 10)
.attr("ry", 10);
//
//
//
//
//
//
//
attach a rectangle
position the left of the rectangle
position the top of the rectangle
set the height
set the width
set the x corner curve radius
set the y corner curve radius
The corners are curved with radii in the x and y direction of 10 pixels.
108
Line
A line is a simple line between two points and is described by four required attributes.
x1: The x position of the first end of the line as measured from the left of the screen.
y1: The y position of the first end of the line as measured from the top of the screen.
x2: The x position of the second end of the line as measured from the left of the screen.
y2: The y position of the second end of the line as measured from the top of the screen.
The following is an example of the code section required to draw a line in conjunction with
the HTML file outlined at the start of this chapter. A notable addition to this code is the style
declaration. In this case the line has no colour and this can be added with the stroke style which
applies a colour to a line;
holder.append("line")
.style("stroke", "black")
.attr("x1", 100)
// x
.attr("y1", 50)
// y
.attr("x2", 300)
// x
.attr("y2", 150);
// y
// attach a line
// colour the line
position of the first end of the line
position of the first end of the line
position of the second end of the line
position of the second end of the line
Line
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_line
109
Polyline
A polyline is a sequence of connected lines described with a single attribute. The d3.js wiki
rightly makes the point that it is typically more convenient and flexible to use the d3.svg.line path
generator in conjunction with a path element. So while drawing a polyline using this method
may be possible, bear in mind that depending on your application, there may be a better way.
points: The points attribute is a list of x,y coordinates that are the locations of the
connecting points of the polyline.
The following is an example of the code section required to draw a polyline in conjunction with
the HTML file outlined at the start of this chapter. A notable addition to this code are the style
declarations. In this case the line of the polyline has no colour and this can be added with the
stroke style which applies the colour black to a line. Likewise the area that is bounded by the
polyline will be automatically filled with black unless we explicitly tell the object not to. This is
achieved in this example by addition of the fill style to none.
holder.append("polyline")
// attach a polyline
.style("stroke", "black") // colour the line
.style("fill", "none")
// remove any fill colour
.attr("points", "100,50, 200,150, 300,50"); // x,y points
Polyline
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_polyline
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_polyline
110
Polygon
A polygon is a sequence of connected lines which form a closed shape described with a single
attribute. The d3.js wiki rightly makes the point that it is typically more convenient and flexible
to use the d3.svg.line path generator in conjunction with a path element. So while drawing a
polygon using this method may be possible, bear in mind that depending on your application,
there may be a better way.
points: The points attribute is a list of x,y coordinates that are the locations of the
connecting points of the polygon. The last point is in turn connected to the first point.
The following is an example of the code section required to draw a polygon in conjunction with
the HTML file outlined at the start of this chapter. A notable addition to this code are the style
declarations. In this case the line of the polygon has no colour and this can be added with the
stroke style which applies the colour black to a line. Likewise the area that is bounded by the
polygon will be automatically filled with black unless we explicitly tell the object not to. This is
achieved in this example by addition of the fill style to none.
holder.append("polygon")
// attach a polygon
.style("stroke", "black") // colour the line
.style("fill", "none")
// remove any fill colour
.attr("points", "100,50, 200,150, 300,50"); // x,y points
Polyline
The polygon extends from the point 100,50 to 200,150 to 300,50 and then back to 100,50.
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_polygon
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_polygon
111
Path
A path is an outline of an SVG shape which is described with a mini-language inside a single
attribute.
d: This attribute is a list of instructions that allow a shape to be drawn in a complex way
using a mini-language of commands. These commands are written in a shorthand of
single letters such as M-moveto, Z-closepath, L-lineto, C-curveto. These commands can be
absolute (normally designated by capital letters) or relative (lower case).
The following is an example of the code section required to draw a triangle in conjunction with
the HTML file outlined at the start of this chapter. A notable addition to this code are the style
declarations. In this case the line of the path has no colour and this can be added with the stroke
style which applies the colour black to a line. Likewise the area that is bounded by the path will
be automatically filled with black unless we explicitly tell the object not to. This is achieved in
this example by addition of the fill style to none.
holder.append("path")
// attach a path
.style("stroke", "black") // colour the line
.style("fill", "none")
// remove any fill colour
.attr("d", "M 100,50, L 200,150, L 300,50 Z"); // path commands
Path
The path mini-language first moves (M) to 100,50 then draws a line (L) to 200,150 then draws
another line (L) to 300,50 then closes the path (Z).
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_path
http://www.w3.org/TR/SVG/paths.html#PathData
112
Text
A text element is an SVG object which is shaped as text. It is described by two required attributes
and three optional ones.
x: This attribute designates the anchor point location for the text in the x dimension
(required).
y: This attribute designates the anchor point location for the text in the y dimension
(required).
dx: This attribute designates the offset of the text from the anchor point in the x dimension
(optional). There are several different sets of units that can be used to designated the offset
of the text from an anchor point. These include em which is a scalable unit (used in these
examples), px (pixels), pt (points (kind of like pixels)) and 5 (percent (scalable and kind of
like em))
dy: This attribute designates the offset of the text from the anchor point in the y dimension
(optional).
text-anchor: This attribute controls the horizontal text alignment (optional). It has three
values; start (left aligned), middle (centre aligned) and end (right aligned).
The following is an example of the code section required to draw the text Hello World in
conjunction with the HTML file outlined at the start of this chapter. A notable addition to this
code is the style declaration which applies a black fill to the text. Additionally there is the
declaration .text which defines the text that will be displayed.
holder.append("text")
.style("fill", "black")
.attr("x", 200)
.attr("y", 100)
.text("Hello World");
//
//
//
//
//
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
define the text to display
Text
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_text
113
It can be seen from the image that the anchor point for the text is at 200,100 and that the text is
positioned with this anchor point at the bottom, left of the text.
The following examples will demonstrate the various options for positioning and aligning text
so that you can arrange it correctly.
Anchor at the bottom, middle of the text:
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 100)
// set y position of bottom of text
.attr("text-anchor", "middle") // set anchor y justification
.text("Hello World");
// define the text to display
114
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
// set anchor y justification
// define the text to display
115
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
// set offset y position
// set anchor y justification
// define the text to display
116
117
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
// set offset y position
// set anchor y justification
// define the text to display
118
Attributes
At the start of writing this section I was faced with the question Whats an attribute?. But
a reasonable answer has eluded me, so I will make the assumption that the answer will be
something of a compromise :-). I like to think that an attribute of an element is something that
is a characteristic of the object without defining it, and/or it may affect the objects position
or orientation on the page. There could be a strong argument to say that the following section
on styles could be seen to cross-over into attributes and I agree. However, for the purposes of
providing a description of the syntax and effects, Im happy with the following list :-).
Because not all attributes are applicable to all elements, there will be a bit of variation in the
type of shapes we deal with in the description below, but there wont be any that are different
to those that weve already looked at. There will be some repetition with recurring information
from the elements section. This is intentional to hopefully allow each section to exist in its own
right.
x, y
The x and y attributes are used to designate a position on the web page that is set from the top,
left hand corner of the web page. Using the x and y attributes places the anchor points for these
elements at a specified location. Of the elements that we have examined thus far, the rectangle
element and the text element have anchor points to allow them to be positioned.
For example the following is a code section required to draw a rectangle (using only the required
attributes) in conjunction with the HTML file outlined at the start of this chapter;
holder.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("height", 100)
.attr("width", 200);
//
//
//
//
//
attach a rectangle
position the left of the rectangle
position the top of the rectangle
set the height
set the width
The top left corner of the rectangle is specified using x and y at 100 and 50 respectively.
119
x1: The x position of the first end of the line as measured from the left of the screen.
y1: The y position of the first end of the line as measured from the top of the screen.
x2: The x position of the second end of the line as measured from the left of the screen.
y2: The y position of the second end of the line as measured from the top of the screen.
The following is an example of the code section required to draw a line in conjunction with the
HTML file outlined at the start of this chapter. The attributes connect the point 100,50 (x1, y1)
with 300,150 (x2, y2);
holder.append("line")
.style("stroke", "black")
.attr("x1", 100)
// x1
.attr("y1", 50)
// y1
.attr("x2", 300)
// x2
.attr("y2", 150);
// y2
// attach a line
// colour the line
position of the first end of the line
position of the first end of the line
position of the second end of the line
position of the second end of the line
Line
120
points
The points attribute is used to set a series of points which are subsequently connected with a
line and / or which may form the bounds of a shape. These are specifically associated with the
polyline and polygon elements. Like the x, y and x1, x2, y1, y2 attributes, the coordinates are
set from the top, left hand corner of the web page.
The data for the points is entered as a sequence of x,y points in the following format;
.attr("points", "100,50, 200,150, 300,50");
Where 100,50 is the first x,y point then 200,150 is the second. Now is probably the best time to
mention that the d3.js wiki makes the point that it is typically more convenient and flexible to
use the d3.svg.line path generator in conjunction with a path element when describing complex
shapes. So while drawing a polyline or polygon using this method may be possible, bear in mind
that depending on your application, there may be a better way.
The following is an example of the code section required to draw a polyline in conjunction with
the HTML file outlined at the start of this chapter. The additional style declarations are included
to illustrate the shape better. The points values can be compared with the subsequent image.
holder.append("polyline")
// attach a polyline
.style("stroke", "black") // colour the line
.style("fill", "none")
// remove any fill colour
.attr("points", "100,50, 200,150, 300,50"); // x,y points
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-svg_polyline
121
cx, cy
The cx, cy attributes are associated with the circle and ellipse elements and designate the centre
of each shape. The coordinates are set from the top, left hand corner of the web page.
cx: The position of the centre of the element in the x axis measured from the left side of
the screen.
cy: The position of the centre of the element in the y axis measured from the top of the
screen.
The following is an example of the code section required to draw an ellipse in conjunction with
the HTML file outlined at the start of this chapter. In it the centre of the ellipse is set by cx, cy
as 200, 100.
holder.append("ellipse")
.attr("cx", 200)
.attr("cy", 100)
.attr("rx", 100)
.attr("ry", 50);
//
//
//
//
//
attach an ellipse
position the x-centre
position the y-centre
set the x radius
set the y radius
The centre of the ellipse is at x = 200 and y = 100 and the radius is 50 pixels vertically and 100
pixels horizontally.
122
r
The r attribute determines the radius of a circle element from the cx, cy position (the centre of
the circle) to the perimeter of the circle.
The following is an example of the code section required to draw a circle in conjunction with
the HTML file outlined at the start of this chapter;
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50);
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
The centre of the circle is at x = 200 and y = 100 and the radius is 50 pixels.
123
rx, ry
The rx, ry attributes are associated with the ellipse element and designates the radius in the x
direction (rx) and the radius in the y direction (ry).
rx: The radius of the ellipse in the x dimension from the cx, cy position to the perimeter
of the ellipse.
ry: The radius of the ellipse in the y dimension from the cx, cy position to the perimeter
of the ellipse.
The following is an example of the code section required to draw an ellipse in conjunction with
the HTML file outlined at the start of this chapter. In it, the centre of the ellipse is set by cx, cy
as 200, 100 and the radius in the x direction (rx) is 100 pixels and the radius in the y direction
(ry) is 50 pixels.
holder.append("ellipse")
.attr("cx", 200)
.attr("cy", 100)
.attr("rx", 100)
.attr("ry", 50);
//
//
//
//
//
attach an ellipse
position the x-centre
position the y-centre
set the x radius
set the y radius
The centre of the ellipse is at x = 200 and y = 100 and the radius is 50 pixels vertically and 100
pixels horizontally.
124
The transform-translate attribute will take an elements position and adjust it based on a specified
value(s) in the x,y directions.
The best way to illustrate this is with an example;
This is the code snippet from the HTML file outlined at the start of this chapter which draws a
circle at the position 200,100 (cx,cy);
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50);
//
//
//
//
attach a circle
position the x-center
position the y-center
set the radius
Circle
If we add in a transform (translate(*x*,*y*)) attribute for values of x,y of 50,50 this will
shift our circle by an additional 50 pixels in the x direction and 50 pixels in the y direction.
Heres the code snippet that will draw our new circle;
125
holder.append("circle")
// attach a circle
.attr("cx", 200)
// position the x-center
.attr("cy", 100)
// position the y-center
.attr("transform", "translate(50,50)") // translate the circle
.attr("r", 50);
// set the radius
Circle
The circle was positioned at the point 200,100 and then translated by 50 pixels in both axes to
250,150.
The original code snippet could in fact be written as follows;
holder.append("circle")
// attach a circle
.attr("transform", "translate(200,100)") // translate the circle
.attr("r", 50);
// set the radius
Since by default our starting position is 0,0 if we apply a translation of 200,100 we will end up at
200,100.
transform (scale(k))
The translate-scale attribute will take an elements attributes and scale them by a factor k.
Originally I thought that this attribute would affect the size of the element, but it affects more
than that! As with the transform-translate attribute, the best way to illustrate this is with an
example;
The following code snippet (in conjunction with the HTML file outlined at the start of this
chapter) which draws a circle at the position 150,50 with a radius of 25 pixels;
holder.append("circle")
.attr("cx", 150)
.attr("cy", 50)
.attr("r", 25);
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
126
Circle
If we now introduce a transform-scale attribute with a scale of 2 we will see all three of the other
attributes (cx, cy and r) scaled by a factor of two to 300, 100 and 50 respectively.
Here is the code;
holder.append("circle")
.attr("cx", 150)
.attr("cy", 50)
.attr("r", 25)
.attr("transform", "scale(2)");
//
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
scale the circle attributes
Circle
In this example we can see that the position (cx, cy) and the radius (r) have been scaled up by a
factor of 2.
transform (rotate(a))
The translate-rotate attribute will rotate an element and its attributes by a declared angle in
degrees.
The ability to rotate elements is obviously a valuable tool. The transform-rotate attribute does a
great job of it, but the key to making sure that you know exactly what will happen to an object is
to remember where the anchor point is for the object and to ensure that the associated attributes
127
are set appropriately. As with the transform translate & scale attributes, the best way to illustrate
this is with an example;
The following is the code snippet (in conjunction with the HTML file outlined at the start of this
chapter) which draws the text Hello World at the position 200,100 with the anchor point being
the the middle of the text;
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 100)
// set y position of bottom of text
.attr("dy", ".35em")
// set offset y position
.attr("text-anchor", "middle") // set anchor y justification
.text("Hello World");
// define the text to display
128
Obviously the text has been rotated, but hopefully youll have noticed that its also been
displaced. This is because the transform-rotate attribute has been applied to both the text element
(which has been rotated by 10 degrees) and the x,y attributes. If you imagine the origin point for
the element being at 0,0, the centre, middle of the text element has been rotated about the point
0,0 by 10 degrees (hopefully slightly better explained in the following picture).
This could be seen as an impediment to getting things to move / change as you want to, but
instead its an indication of a different way of doing things. The solution to this particular feature
is to combine the transform-rotate with the transform-translate that we used earlier so that the
code looks like this;
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("dy", ".35em")
// set offset y position
.attr("text-anchor", "middle") // set anchor y justification
.attr("transform", "translate(200,100) rotate(10)")
.text("Hello World");
// define the text to display
129
Which leads us to the final example for which is a combination of all three aspects of the
transform attribute.
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("dy", ".35em")
// set offset y position
.attr("text-anchor", "middle") // set anchor y justification
.attr("transform", "translate(200,100) scale(2) rotate(10)")
.text("Hello World");
// define the text to display
Here we have a text element translated to its position on the page, rotated by 10 degrees about
the centre of the text and scaled by a factor of two.
130
width, height
width and height are required attributes of the rectangle element. width designates the width
of the rectangle and height designates the height (If youre wondering, I often struggle defining
the obvious).
The following is an example of the code section required to draw a rectangle (using only the
required attributes) in conjunction with the HTML file outlined at the start of this chapter;
holder.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("height", 100)
.attr("width", 200);
//
//
//
//
//
attach a rectangle
position the left of the rectangle
position the top of the rectangle
set the height
set the width
Rectangle
The width of the triangle is 200 pixels and the height is 100 pixels.
131
text-anchor
The text-anchor attribute determines the justification of a text element
Text can have one of three text-anchor types;
start where the text is left justified.
middle where the text is centre justified.
end where the text is right justified.
The following is an example of code that will draw three separate lines of text with the three
different text-anchor types in conjunction with the HTML file outlined at the start of this
chapter;
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 50)
// set y position of bottom of text
.attr("text-anchor", "start") // set anchor y justification
.text("Hello World - start"); // define the text to display
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 100)
// set y position of bottom of text
.attr("text-anchor", "middle") // set anchor y justification
.text("Hello World - middle"); // define the text to display
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 150)
// set y position of bottom of text
.attr("text-anchor", "end") // set anchor y justification
.text("Hello World - end"); // define the text to display
132
dx, dy
dx and dy are optional attributes that designate an offset of text elements from the anchor point
in the x and y dimension . There are several different sets of units that can be used to designate
the offset of the text from an anchor point. These include em which is a scalable unit, px (pixels),
pt (points (kind of like pixels)) and % (percent (scalable and kind of like em))
We can demonstrate the offset effect by noting the difference in two examples.
The first is a simple projection of SVG text that aligns the text Hello World above and to the
right of the anchor point at 200,100 (It does this in conjunction with the HTML file outlined at
the start of this chapter.).
holder.append("text")
.style("fill", "black")
.attr("x", 200)
.attr("y", 100)
.text("Hello World");
//
//
//
//
//
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
define the text to display
The second example introduces the dx attribute setting the offset to 50 pixels. This adds another
50 pixels to the x dimension. We also introduce the dy attribute with an offset of .35em. This
scalable unit allows the text to be set as a factor of the size of the text. In this case .35em will add
half the height of the text to the y dimension placing the text so that it is exactly in the middle
(vertically) of the 100 pixel line on the y dimension.
holder.append("text")
.style("fill", "black")
.attr("x", 200)
.attr("y", 100)
.attr("dx", "50px")
.attr("dy", ".35em")
.text("Hello World");
//
//
//
//
//
//
//
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
set offset x position
set offset y position
define the text to display
133
The text has been moved 50 pixels to the right and half the height of the text down the page.
134
textLength
The textLength attribute adjusts the length of the text to fit a specified value.
The following is a code snippet that prints the text Hello World above and to the right of the
anchor point at 200,100 (It does this in conjunction with the HTML file outlined at the start of this
chapter.). The addition of the textLength attribute declaration in the code stretches the Hello
World out so that it fills 150 pixels.
holder.append("text")
.style("fill", "black")
.attr("x", 200)
.attr("y", 100)
.attr("textLength", "150")
.text("Hello World");
//
//
//
//
//
//
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
set text length
define the text to display
It is worth noting that while the text has been spread out, the individual letters remain unstretched. Only the letter and word spacing has been adjusted. However, using the lengthAdjust
attribute can change this.
135
lengthAdjust
The lengthAdjust attribute allows the textLength attribute to have the spacing of a text element
controlled to be either spacing or spacingAndGlyphs;
spacing: In this option the letters remain the same size, but the spacing between the letters
and words are adjusted.
spacingAndGlyphs: In this option the text is stretched or squeezed to fit.
The attribute can be best illustrated via an example. The following code snippet (which works
in conjunction with the HTML file outlined at the start of this chapter) shows three versions of
the text element. The top line is the standard text. The middle line is the textLength set to 150
and the lengthAdjust set to spacing (which is the default). The bottom line is the textLength
set to 150 and the lengthAdjust set to spacingAndGlyphs.
holder.append("text")
.style("fill", "black")
.attr("x", 200)
.attr("y", 50)
.text("Hello World");
//
//
//
//
//
append text
fill the text with the colour black
set x position of left side of text
set y position of bottom of text
define the text to display
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 100)
// set y position of bottom of text
.attr("textLength", "150") // set text length
.attr("lengthAdjust", "spacing")
.text("Hello World");
// define the text to display
holder.append("text")
// append text
.style("fill", "black")
// fill the text with the colour black
.attr("x", 200)
// set x position of left side of text
.attr("y", 150)
// set y position of bottom of text
.attr("textLength", "150") // set text length
.attr("lengthAdjust", "spacingAndGlyphs")
// define the text to display
.text("Hello World");
136
The image shows that the top line looks normal, the middle line has had the spaces increased to
increase the length of the text and the bottom line has been stretched.
137
Styles
Whats a style?
Believe it or not, thats as difficult a question to answer as Whats an attribute?. I like to think
that an element can be selected and arranged on a web page with select and attr, but once
its there, changes to how it looks are a matter for style. We will cover a range of qualities
that neatly fit into this definition in the following section (such as fill, opacity and stroke-width)
but there are also a range of unusual style declarations that many may not have come across (I
certainly hadnt before writing this).
The other important thing to mention about setting styles for elements is that there are different
ways to accomplish the task. Well go through the process of describing different styles as they
can be applied to individual elements in isolation, but there is a more powerful way to manage
styles across a range of elements via Cascading Style Sheets (CSS) in the <style> section of a
web page or even via an external style sheet. We will examine these possibilities at the end of
the section.
Full disclosure: I have not figured out how to work some of the styles for d3.js Im afraid that
clip-path and mask have exceeded my skill-set and I will have to leave them for another day :-(.
I found that there are several good examples that make use of these styles, but I have struggled
(unsuccessfully) to present them in a simple example.
138
fill
The fill style will fill the element being presented with a specified colour.
By default, most elements will be filled with black (the majority of the examples used in this
chapter make no fill declaration).
The following example (which works in conjunction with the HTML file outlined at the start of
this chapter) shows the syntax for filling a simple circle with the colour red;
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50)
.style("fill", "red");
//
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
set the fill colour
As we saw with the polyline and polygon examples earlier in the chapter some shapes may
need to have their fill colour turned off in some circumstances and this can be accomplished
by declaring the colour to be none (.style("fill", "none");).
There are several different ways to define exactly what colour we want as a fill. The example
above uses a named colour code to declare the colour as red but we could also have defined
it as rgb (.style("fill", "rgb(255,0,0)");) or in hexadecimal (.style("fill", "#f00");)
139
stroke
The stroke style applies a colour to lines.
By default many elements do not have a stroke colour set, so its a matter of declaring the colour
with either a named colour code (red), an rgb value (rgb(255,0,0)) or the appropriate hex
(#f00).
The following example (which works in conjunction with the HTML file outlined at the start of
this chapter) shows the syntax for applying the colour red to a simple circle. The fill has been set
to none to help the colour stand out.
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50)
.style("stroke", "red")
.style("fill", "none");
//
//
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
set the line colour
set the fill colour
140
opacity
The opacity style has the effect of varying an elements transparency.
The valid range for opacity is from 0 (completely transparent) to 1 (solid colour). We should
make the distinction at this point that opacity affects the entire element, whereas the following
fill-opacity and stroke-opacity affects only the fill and stroke respectively.
The following code snippet (which works in conjunction with the HTML file outlined at the start
of this chapter) creates a green circle with a red border. The opacity value of .2 creates a degree
of transparency which will show the grid lines underneath the element.
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50)
.style("opacity", .2)
.style("stroke", "red")
.style("fill", "green");
//
//
//
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
set the element opacity
set the line colour
set the fill colour
141
fill-opacity
The fill-opacity style changes the transparency of the fill of an element.
The valid range for fill-opacity is from 0 (completely transparent) to 1 (solid colour). We
should make the distinction at this point that fill-opacity affects only the fill of an element,
whereas opacity will affect the entire element.
The following code snippet (which works in conjunction with the HTML file outlined at the start
of this chapter) creates a green circle with a red border. The opacity value of .2 creates a degree
of transparency for the fill which will show the grid lines underneath.
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50)
.style("fill-opacity", .2)
.style("stroke", "red")
.style("fill", "green");
//
//
//
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
set the fill opacity
set the line colour
set the fill colour
The distinction between this image and the one for the opacity style clearly shows the line
around the outside of the object as still a solid (opaque) colour.
142
stroke-opacity
The stroke-opacity style changes the transparency of the stroke (line) of an element.
The valid range for stroke-opacity is from 0 (completely transparent) to 1 (solid colour). We
should make the distinction at this point that stroke-opacity affects only the line or border of
an element, whereas opacity will affect the entire element.
The following code snippet (which works in conjunction with the HTML file outlined at the start
of this chapter) creates an empty circle with a red border. The opacity value of .2 creates a degree
of transparency for the stroke which will show the grid lines underneath (or at least make it
appear more muted).
holder.append("circle")
// attach a circle
.attr("cx", 200)
// position the x-centre
.attr("cy", 100)
// position the y-centre
.attr("r", 50)
// set the radius
.style("stroke-opacity", .2) // set the stroke opacity
.style("stroke", "red")
// set the line colour
.style("fill", "none");
// set the fill colour
Although it is not necessarily easy to see in this example because the line is quite thin, the lines
of the grid behind the circle will be showing through the line of the circle.
143
stroke-width
The stroke-width style adjusts the width of the line of an element.
The value specified when setting stroke-width is in pixels.
The following code snippet (which works in conjunction with the HTML file outlined at the start
of this chapter) creates an empty circle with a red border. The stroke-width is set to 5 which
equates to 5 pixels (it can also be specified as 5px).
holder.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 50)
.style("stroke-width", 5)
.style("stroke", "red")
.style("fill", "none");
//
//
//
//
//
//
//
attach a circle
position the x-centre
position the y-centre
set the radius
set the stroke width
set the line colour
set the fill colour
The width of the line that forms the border of the circle is now 5 pixels wide :-).
144
stroke-dasharray
The stroke-dasharray style allows us to form element lines with dashes instead of solid lines.
We have covered dashed lines in practical way in a previous section of the book (Make a Dashed
Line) but for the sake of completeness I will include dashed lines here as well.
We create a dashed line by specifying the length of a dash and then the length of a space. We
can include a long list of dashes and spaces and once complete our line will simply repeat the
pattern we have specified.
For example the following code snippet (which works in conjunction with the HTML file outlined
at the start of this chapter) creates a line with a dash of 10 pixels followed by a space of 2 pixels;
holder.append("circle")
// attach a circle
.attr("cx", 200)
// position the x-centre
.attr("cy", 100)
// position the y-centre
.attr("r", 50)
// set the radius
.style("stroke-dasharray", ("10,3")) // make the stroke dashed
.style("stroke", "red")
// set the line colour
.style("fill", "none");
// set the fill colour
More complex combinations of dashes and spaces are possible as are complex animation
sequences that leverage the ability to move objects along a path (these are certainly more
advanced examples).
145
stroke-linecap
The stroke-linecap style allows control of the shape of the ends of lines in d3.js.
There are three shape options;
butt where the line simply butts up to the starting or ending position and is cut off
squarely.
round where the line is rounded in proportion to its width.
square where the line is squared off but extended in proportion to its width.
The following code snippet (which works in conjunction with the HTML file outlined at the
start of this chapter) generates three lines showing each stroke-linecap style option. The top
line uses butt. The middle line uses round and the bottom line uses square.
holder.append("line")
// attach a line
.style("stroke", "black")
// colour the line
.style("stroke-width", 20)
// adjust line width
.style("stroke-linecap", "butt") // stroke-linecap type
.attr("x1", 100)
// x position of the first end of the line
.attr("y1", 50)
// y position of the first end of the line
.attr("x2", 300)
// x position of the second end of the line
.attr("y2", 50);
// y position of the second end of the line
holder.append("line")
.style("stroke", "black")
.style("stroke-width", 20)
.style("stroke-linecap", "round")
.attr("x1", 100)
// x position
.attr("y1", 100)
// y position
.attr("x2", 300)
// x position
.attr("y2", 100);
// y position
//
//
//
//
of
of
of
of
holder.append("line")
.style("stroke", "black")
.style("stroke-width", 20)
.style("stroke-linecap", "square")
.attr("x1", 100)
// x position
.attr("y1", 150)
// y position
.attr("x2", 300)
// x position
.attr("y2", 150);
// y position
// attach a line
// colour the line
// adjust line width
// stroke-linecap type
of the first end of the line
of the first end of the line
of the second end of the line
of the second end of the line
attach a line
colour the line
adjust line width
stroke-linecap type
the first end of the line
the first end of the line
the second end of the line
the second end of the line
146
The shapes are quite distinct for each type and it is useful to note the degree to which the lines
extend beyond their start and end points.
147
stroke-linejoin
The stroke-linejoin style specifies the shape of the join of two lines. This would be used on
path, polyline and polygon elements (and possibly more).
There are three line join options;
miter where the join is squared off as would be expected at the join of two lines.
round where the outside portion of the join is rounded in proportion to its width.
bevel where the join has a straight edged outer portion clipped off to provide a slightly
more contoured effect while still being angular.
The following code snippet (which works in conjunction with the HTML file outlined at the
start of this chapter) generates a poly line where the join has the connection shaped using the
stroke-linejoin round style.
holder.append("polyline")
// attach a polyline
.style("stroke", "black")
// colour the line
.style("fill", "none")
// remove any fill colour
.style("stroke-width", 20) // colour the line
.style("stroke-linejoin", "round") // shape the line join
.attr("points", "100,50, 200,150, 300,50"); // x,y points
148
Here we can see the clipping of the outer portion of the join.
And using miter produces a standard connection;
This is the default setting for line joins and does not need to be added unless the line join type
has already been set to a different default.
149
writing-mode
The writing-mode style changes the orientation of the text so that it prints out top to bottom. It
has a single option tb that accomplishes this. It is relatively limited in scope compared to the
equivalent for CSS, but for the purposes of generating some text it has a definite use.
The following code snippet (hich works in conjunction with the HTML file outlined at the start
of this chapter) creates a line of text that is now printed from top to bottom instead of left to
right.
holder.append("text")
// append text
.style("fill", "black")
// make the text black
.style("writing-mode", "tb") // set the writing mode
.attr("x", 200)
// set x position of left side of text
.attr("y", 100)
// set y position of bottom of text
.text("Hello World");
// define the text to display
It is significant to note that while it looks like the text has been rotated about its anchor
point, this actually isnt the case since the anchor point should be at 200,100. Also, the
glyph-orientation-vertical style (which follows) will allow the text to be orientated vertically
which will be useful.
150
glyph-orientation-vertical
The glyph-orientation-vertical style changes the rotation of the individual glyphs (characters) in text and if used in conjunction with the writing-mode style (and set to 0) will allow the
text to be displayed vertically with the letters orientated vertically as well.
The following code snippet (which works in conjunction with the HTML file outlined at the
start of this chapter) creates a line of text that is now printed from top to bottom with letters
orientated vertically.
holder.append("text")
// append text
.style("fill", "black")
// make the text black
.style("writing-mode", "tb") // set the writing mode
.style("glyph-orientation-vertical", 0)
.attr("x", 200)
// set x position of left side of text
.attr("y", 25)
// set y position of bottom of text
.text("Hello World");
// define the text to display
It is worth noting that the text spacing increases dramatically as the spacing for each letter relies
on the normal distance between the bottom and top of a line of text.
151
//
//
//
//
of
of
of
of
holder.append("line")
.style("stroke", "black")
.style("stroke-width", 20)
.style("stroke-linecap", "square")
.attr("x1", 100)
// x position
.attr("y1", 150)
// y position
.attr("x2", 300)
// x position
.attr("y2", 150);
// y position
// attach a line
// colour the line
// adjust line width
// stroke-linecap type
of the first end of the line
of the first end of the line
of the second end of the line
of the second end of the line
attach a line
colour the line
adjust line width
stroke-linecap type
the first end of the line
the first end of the line
the second end of the line
the second end of the line
152
The block of code for each of the three lines contains three separate style declarations. Two of
which are identical for all three blocks of code;
.style("stroke", "black")
.style("stroke-width", 20)
To make these styles available from a common point, we declare them in the <style> section of
our HTML file as follows;
<style>
line.linecap {
stroke: black;
stroke-width: 20;
}
</style>
The <style> tags simply tell our browser which part of the html file we are using to define our
styles.
The line.linecap portion identifies the following styles as belonging to the line elements that
are also identified as belonging to the class linecap (We have used the linecap name as a
convenience only and it could just as easily been foobar.).
The two styles are enclosed within curly braces and are declared in the form <style-name>:
<style-value>;. So for our example here, the stroke is black and its width is 20 pixels.
Then our example script can have the two styles removed from each of the blocks that draws the
lines and in their place we add a new attribute class that assigns a class to the element (in this
case the class linecap). Our new code will look like this;
153
holder.append("line")
// attach a line
.style("stroke-linecap", "butt") // stroke-linecap type
.attr("class", "linecap")
// inherits styles from CSS
.attr("x1", 100)
// x position of the first end of the line
.attr("y1", 50)
// y position of the first end of the line
.attr("x2", 300)
// x position of the second end of the line
.attr("y2", 50);
// y position of the second end of the line
holder.append("line")
// attach a line
.style("stroke-linecap", "round") // stroke-linecap type
.attr("class", "linecap")
// inherits styles from CSS
.attr("x1", 100)
// x position of the first end of the line
.attr("y1", 100)
// y position of the first end of the line
.attr("x2", 300)
// x position of the second end of the line
.attr("y2", 100);
// y position of the second end of the line
holder.append("line")
// attach a line
.style("stroke-linecap", "square") // stroke-linecap type
.attr("class", "linecap")
// inherits styles from CSS
.attr("x1", 100)
// x position of the first end of the line
.attr("y1", 150)
// y position of the first end of the line
.attr("x2", 300)
// x position of the second end of the line
.attr("y2", 150);
// y position of the second end of the line
While this has only replaced two lines with one in our code, the potential for use in far more
complex examples should be obvious. There is significantly more detail that can be gone into
with regard to CSS, but that would be beyond my meagre abilities.
All you need to do is take the simple graph example file and slot the following block in between
the Add the valueline path and the add the x axis blocks.
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); });
155
I deliberately put the dots after the line in the drawing section, because I thought they would
look better, but you could put the block of code before the line drawing block to get the following
effect;
(just trying to reinforce the concept that order matters when drawing objects :-)).
You could of course just remove the line block all together
156
Then we add a circle for each data point (.enter().append("circle")) with a radius of 3.5
pixels (.attr("r", 3.5)) and appropriate x (.attr("cx", function(d) { return x(d.date);
})) and y (.attr("cy", function(d) { return y(d.close); });) coordinates.
There is lots more that we could be doing with this piece of code (check out the scatter plot
example) including varying the colour or size or opacity of the circles depending on the data
and all sorts of really neat things, but for the mean time, there we go. Scatter plot!
Ive placed a copy of the file for drawing the scatter plot into the downloads section on d3noob.org
with the general examples as simple-scatterplot.html.
http://bl.ocks.org/3887118
157
Adding tooltips.
Tooltips have a marvellous duality. They are on one hand a pretty darned useful thing that aids
in giving context and information where required and on the other hand, if done with a bit of
care, they can look very stylish :-).
Technically, they represent a slight move from what we have been playing with so far into a
mildly more complex arena of transitions and events. You can take this one of two ways.
Either accept that it just works and implement it as shown, or you will know whats going on
and feel free to deride my efforts as those of a rank amateur :-).
The source for the implementation was taken from Mike Bostocks example on
bl.ocks.org. This was combined with a few other bits and pieces (the trickiest being
working out how to format the displayed date correctly and inserting a line break in
the tooltip (which I found on Google Groups; (well done to all those participating
in that discussion)). I make the assumption that any or all errors that occur in the
implementation will be mine, whereas, any successes will be down to the original
contributors.
Just in case there is some confusion, a tooltip (one word or two?) is a discrete piece of information
that will pop into view when the mouse hovers over somewhere specific. Most of us have seen
and used them, but I suppose we all tend to call them different things such as infotip, hint or
hover box I dont know if theres a right name for them, but heres an example of what were
trying to achieve;
You can see the mouse has hovered over one of the scatter plot circles and a tip has appeared
that provides the user with the exact date and value for that point.
Now, you may also notice that theres a certain degree of fancy here as the information is bound
by a rectangular shape with rounded corners and a slight opacity. The other piece of fancy
which you dont see in a PDF (or whatever format this distinguished tome will be published in
on its 33rd reprint in the year 2034), is that when these tool tips appear and disappear, they do
so in an elegant fade-in, fade-out way. Purty.
http://bl.ocks.org/1087001
https://groups.google.com/forum/?fromgroups=#!topic/d3-js/GgFTf24ltjc
158
Now, before we get started describing how the code goes together, lets take a quick look at the
two technique specifics that I mentioned earlier, transitions and events.
Transitions
From the main d3.js web page (d3js.org) transitions are described as gradually interpolating styles
and attributes over time. So what I take that to mean is that if you want to change an object, you
can do so be simply specifying the attribute / style end point that you want it to end up with and
the time you want it to take and go!
Of course, its not quite that simple, but luckily, smarter people than I have done some fantastic
work describing different aspects of transitions so please see the following for a more complete
description of the topic;
Mike Bostocks Bar chart tutorial
Christophe Viaus Try D3 Now! tutorial
Hopefully observing the mouseover and mouseout transitions in the tooltips example will whet
your appetite for more!
Events
The other technique is related to mouse events. This describes the browser watching for when
something happens with the mouse on the screen and when it does, it takes a specified action.
A (probably non-comprehensive) list of the types of events are the following;
How many of these are valid to use within d3 Im not sure, but Im willing to bet that there are
probably more than those here as well. Please go to http://javascript.info/tutorial/mouse-events
for a far better description of the topic if required.
http://mbostock.github.com/d3/tutorial/bar-2.html
http://christopheviau.com/d3_tutorial/
http://javascript.info/tutorial/mouse-events
159
Get tipping
So, bolstered with a couple of new concepts to consider, lets see how they are enacted in practice.
If we start with our simple-scatter plot graph there are 4 areas in it that we will want to modify
(it may be easier to check the tooltips.html file in the example files in the downloads section on
d3noob.org).
The first area is the CSS. The following code should be added just before the </style> tag;
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
These styles are defining how our tooltip will appear . Most of them are fairly straight forward.
The position of the tooltip is done in absolute measurements, not relative. The text is centre
aligned, the height, width and colour of the rectangle is 28px, 60px and lightsteelblue respectively.
The padding is an interesting feature that provides a neat way to grow a shape by a fixed amount
from a specified size.
We set the border to 0px so that it doesnt show up and a neat style (attribute?) called borderradius provides the nice rounded corners on the rectangle.
Lastly, but by no means least, the pointer-events: none line is in place to instruct the mouse
event to go through the element and target whatever is underneath that element instead
(Read more here). That means that even if the tooltip partly obscures the circle, the code will
still act as if the mouse is over only the circle.
The second addition is a simple one-liner that should (for forms sake) be placed under the
parseData variable declaration;
var formatTime = d3.time.format("%e %B");
This line formats the date when it appears in our tooltip. Without it, the time would default to
a disturbingly long combination of temporal details. In the case here we have declared that we
want to see the day of the month (%e) and the full month name(%B).
The third block of code is the function declaration for div.
https://developer.mozilla.org/en-US/docs/CSS/pointer-events
160
We can place that just after the valueline definition in the JavaScript. Again theres not too
much here thats surprising. We tell it to attach div to the body element, we set the class to the
tooltip class (from the CSS) and we set the opacity to zero. It might sound strange to have the
opacity set to zero, but remember, thats the natural state of a tooltip. It will live unseen until its
moment of revelation arrives and it pops up!
The final block of code is slightly more complex and could be described as a mutant version of
the neat little bit of code that we used to do the drawing of the dots for the scatter plot. Thats
because the tooltips are all about the scatter plot circles. Without a circle to mouseover, the
tooltip never appears :-).
So heres the code that includes the scatter plot drawing (its included since its pretty much
integral);
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div
.html(formatTime(d.date) + "<br/>" + d.close)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
Before we start going through the code, the example file for tooltips that is on
d3noob.org includes a brief series of comments for the lines that are added or changed
from the scatter plot, so if you want to compare what is going on in context, that is an
option.
The first six lines of the code are a repeat of the scatter plot drawing script. The only changes are
that weve increased the radius of the circle from 3.5 to 5 (just to make it easier to mouse over
161
the object) and weve removed the semicolon from the cy attribute line since the code now has
to carry on.
So the additions are broken into two areas that correspond to the two events. mouseover and
mouseout. When the mouse moves over any of the circles in the scatter plot, the mouseover
code is executed on the div element. When the mouse is moved off the circle a different set of
instructions are executed.
on.mouseover
The .on("mouseover" line initiates the introduction of the tooltip. Then we declare the element
we will be introducing (div) and that we will be applying a transition to its introduction
(.transition()). The next two lines describe the transition. It will take 200 milliseconds
(.duration(200)) and will result in changing the elements opacity to .9 (.style("opacity",
.9);). Given that the natural state of our tooltip is an opacity of 0, this make sense for something
appearing, but it doesnt go all the way to a solid object and it retains a slight transparency just
to make it look less permanent.
The following three lines format our tooltip. The first one adds an html element that contains
our x and y information (the date and the d.close value). Now this is done in a slightly strange
way. Other tooltips that I have seen have used a .text element instead of a .html one, but I
have used .html in this case because I wanted to include the line break tag <br/> to separate
the date and value. Im sure there are other ways to do it, but this worked for me. The other
interesting part of this line is that this is where we call our time formatting function that we
described earlier. The next two lines position the tooltip on the screen and to do this they grab
the x and y coordinates of the mouse when the event takes place (with the d3.event.pageX and
d3.event.pageY snippets) and apply a correction in the case of the y coordinate to raise the
tooltip up by the same amount as its height (28 pixels).
on.mouseout
The .on("mouseout" section is slightly simpler in that it doesnt have to do any fancy text / html
/ coordinate stuff. All it has to do is to fade out the div element. And that is done by simply
reversing the opacity back to 0 and setting the duration for the transition to 500 milliseconds
(being slightly longer than the fade-in makes it look slightly cooler IMHO).
Right, there you go. As a description its ended up being a bit of a wall of text Im afraid. But
hopefully between the explanation and the example code you will get the idea. Please take the
time to fiddle with the settings described here to find the ones that work for you and in the
162
process you will reinforce some of the principles that help D3 do its thing. Ive placed a copy
of the file for drawing the tooltips into the downloads section on d3noob.org with the general
examples as tooltips.html.
163
and
.style("fill", "#4682b4")
and
.style("fill", "rgb(70,130,180)")
http://webdesign.about.com/od/colorcharts/l/bl_namedcolors.htm
164
Err Yes, for those among you who are of the observant persuasion, I have deliberately coloured
them red as well (red for DANGER!).
This is a fairly simple example, but serves to illustrate the principle adequately. From our simple
scatter plot example we only need to add in two lines to the block of code that draws the circles
as follows;
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.filter(function(d) { return d.close < 400 })
// <== This line
.style("fill", "red")
// <== and this one
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); });
The first added line uses the .filter function to act on the data points and according to the
arguments passed to it in this case, only return those where the value of d.close is less than 400
(return d.close < 400).
The second added line is our line that simply colours the circles red (.style("fill", "red")).
Thats all there is to it. Pretty simple, but the filter function can be very powerful when used
wisely.
165
Ive placed a copy of the file for selecting / filtering into the downloads section on d3noob.org
with the general examples as filter-selection.html.
166
Starting with the simple scatter plot example all we have to do is include the if statement in the
block of code that draws the circles. Heres the entire block with the additions highlighted;
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.style("fill", function(d) {
// <== Add these
if (d.close <= 400) {return "red"} // <== Add these
else
{ return "black" }
// <== Add these
;})
// <== Add these
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); });
Our first added line introduces the style modifier and the rest of the code acts to provide a return
for the fill attribute.
The second line introduces our if statement. Theres very little difference using if statements
between languages. Just look out for maintaining the correct syntax and you should be fine. In
this case were asking if the value of d.close is less than or equal to 400 and if it is it will return
the "red" statement for our fill.
The third line covers our rear and make sure that if the colour isnt going to be red, its going to
be black. The last line just closes the style and function statements.
The result?
167
Aww.. nice.
Ive placed a copy of the file that uses the if statement into the downloads section on d3noob.org
with the general examples as if-statement.html.
Could it be any cooler? Im glad you asked.
What if we wanted to have all the points where close was less than 400 red and all those where
close was greater than 620 green? Oh yeah! Now were talking.
So with one small change to the if statement;
.style("fill", function(d) {
if (d.close <= 400) {return "red"}
else if (d.close >= 620) {return "lawngreen"} // <== Right here
else { return "black" }
;})
Check it out
Nice.
168
169
In this case the stroke (the colour of the line) is being determined at a link within the page which
is set by the anchor #line-gradient. We will see shortly that this is in our second block of code,
so the colour is being defined in a separate portion of the script.
And now the JavaScript gradient code;
svg.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", y(0))
.attr("x2", 0).attr("y2", y(1000))
.selectAll("stop")
.data([
{offset: "0%", color: "red"},
{offset: "40%", color: "red"},
{offset: "40%", color: "black"},
{offset: "62%", color: "black"},
{offset: "62%", color: "lawngreen"},
{offset: "100%", color: "lawngreen"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
So, our first line adds our linear gradient. Gradients consist of continuously smooth colour
transitions along a vector from one colour to another We can have a linear one or a radial one and
depending on which you select, there are a few options to define. There is some great information
on gradients at http://www.w3.org/TR/SVG/pservers.html (more than I ever thought existed).
The second line (.attr("id", "line-gradient")) sets our anchor for the CSS that we saw
earlier.
The third fourth and fifth lines define the bounds of the area over which the gradient will
act. Since the coordinates x1, y1, x2, y2 will describe an area. The values for y1 (0) and y2
(1000) are used more for convenience to align with our data (which has a maximum value
around 630 or so). For more information on the gradientUnits attribute I found this page useful
https://developer.mozilla.org/en-US/docs/SVG/Attribute/gradientUnits. Well come back to the
coordinates in a moment.
http://www.w3.org/TR/SVG/pservers.html
https://developer.mozilla.org/en-US/docs/SVG/Attribute/gradientUnits
170
The next block selects all the stop elements for the gradients. These stop elements define where
on the range covered by our coordinates the colours start and stop. These have to be defined as
either percentages or numbers (where the numbers are really just percentages in disguise (i.e.
45% =0.43)).
The best way to consider the stop elements is in conjunction with the gradientUnits. The image
following may help.
In this case our coordinates describe a vertical line from 0 to 1000. Our colours transition from
red (0) to red (400) at which point they change to black (400) and this will continue until it gets to
black (620). Then this changes to green (620) and from there, any value above that will be green.
Now, it might seem a little convoluted to be doubling up on the colours and values, but
the reason is that the gradient functions have a lot more to them than were using and
well have a look at the possibilities once the explanation of the code is done.
So after defining the stop elements, we enter and append the elements to the gradient (.enter().append("stop")
with attributes for offset and colour that we defined in the stop elements area.
Now, that IS cool, but by now, I hope that you have picked that a gradient function really does
mean a gradient, and not just a straight change from one colour to another.
So, lets try changing the stop element offsets to the following (and making the stroke-width
slightly larger to see more clearly whats going on);
171
.data([
{offset:
{offset:
{offset:
{offset:
{offset:
{offset:
])
And here we go
172
Weve defined the styles for the area this time, but instead of the stroke being defined by the
separate script, now its the area. While weve changed the url name, its actually the same piece
of code, with a different id (because it seemed wrong to be talking about an area when the label
said line). Weve also set the stroke width to zero, because we dont want any lines around our
filled area.
Now we want to take the block of code that defined our line
var
valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
and we need to replace it with the standard block that defined an area fill.
var
area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
So were not going to be drawing a line at all. Just the area fill.
Next, as I mentioned earlier, we change the id for the linearGradient block from "line-gradient"
to "area-gradient"
173
.attr("id", "area-gradient")
And lastly, we remove the block of code that drew the line and replace it with a block that draws
an area. So change this.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
to this;
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
For a slightly nicer looking example, you could check out a variation of one of Mike Bostocks
originals here; http://bl.ocks.org/4433087.
http://bl.ocks.org/4433087
174
Bitmaps
A bitmap (or raster) image is one that is composed of lots of discrete individual dots (lets call
them pixels) which, when joined together (and zoomed out a bit) give the impression of an image.
If we use the example of the force layout example we developed, and look at a screen shot (and
its important to remember that this is a screen shot) of the image we see a picture that looks
fairly benign.
However, as we enlarge the image by doubling its size (x 2) we begin to see some rough edges
appear.
A bitmap at 200%
175
A bitmap at 400%
A bitmap at 800%
Doubling again for the last time (x 16) and the pixels are plainly evident.
A bitmap at 1600%
Bitmaps can be saved in a wide range of formats depending on users requirements including
compression, colour depth, transparency and a host of other attributes. Typically they can be
identified by the file suffix .jpg, .png or .bmp (and there are an equally large number of other
suffixes).
176
This will be the type of format that most people will be familiar with for images and their ubiquity
with the advent of digital cameras almost makes it redundant to describe them.
However, there is another type of image and it is even more important to d3.js users.
A SVG at 200%
177
A SVG at 400%
Doubling again (x 8) and we can see that the text James is actually composed of a fill colour
and a border.
A SVG at 800%
Doubling again for the last time (x 16) everything still retains its clear sharp edges.
A SVG at 1600%
178
2. Open the SVG image in a program designed to use vector images and edit it if required.
3. Export that image as a bitmap
Copying the image off the web page
Getting the image out of a web page is made easy by using SVG Crowbar. This is a A Chromespecific bookmarklet that extracts SVG nodes and accompanying styles from an HTML document
and downloads them as an SVG file. What that means is that once you drag the bookmarklet
from the web page to your bookmarks (You need to be using Google Chrome, and Im told that
about 60% of the people who visit d3noob.org do) youre ready to go.
Drag the SVG Crowbar Object from the web page to your bookmarks bar
Now when you have a web page open thats displaying a D3 creation, all you need to do is click
on the SVG Crowbar bookmark and you will be prompted for a location to save a svg image.
Really. Its that simple.
Open the SVG Image and Edit
Obviously now that you have a SVG image, you need to be able to do something with it. My
preferred software for this is Inkscape.
Inkscape is An Open Source vector graphics editor, with capabilities similar to Illustrator,
CorelDraw, or Xara X, using the W3C standard Scalable Vector Graphics (SVG) file format.
It really is an extremely capable drawing program and it is capable of a lot more than the job
were going to use it for, so you may find it has other uses that may be valuable.
Once installed, you can open the saved file directly into Inkscape.
http://nytimes.github.io/svg-crowbar/
http://inkscape.org/
179
While here you can edit the drawing to your hearts delight. I particularly recommend ungrouping
the diagram and removing or adjusting individual elements if required.
Once you have finished editing, you are ready for the final step.
Saving as a bitmap
While still in Inkscape, go to the File, Export Bitmap menu.
180
This will open a dialog box where you can select an appropriate resolution and location for your
bitmap and then press the export button.
181
It is worth knowing that the default settings here will export the diagram with a transparent
background (using *.png) which will fit in nicely with a wide range of graphical end uses.
182
The code was drawn from an example provided by Shawn Allen on Google Groups. In fact,
the post itself is an excellent one if you are considering creating a table straight from a csv file.
http://jsfiddle.net/7WQjr/
http://stackoverflow.com/questions/9268645/d3-creating-a-table-linked-to-a-csv-file
183
HTML Tables
Im walking a fine line here since I have a remarkably small amount of knowledge on
HTML tables. So Ill try to provide a brief overview as I understand it and as I see it
represented in the code below, but for a far fuller explanation, take a look at some great
work by Peter Cook here or let Google be your friend.
Tables are made up of rows, columns and data (that goes in each cell). All you need to do to
successfully place a table on a web page is to lay out the rows and columns in a logical sequence
using the appropriate HTML tags and youre away.
For example heres the total HTML code for a web page to display a simple table;
<!DOCTYPE html>
<body>
<table border="1">
<tr>
<th>Header
<th>Header
</tr>
<tr>
<td>row 1,
<td>row 1,
</tr>
<tr>
<td>row 2,
<td>row 2,
</tr>
</table>
</body>
1</th>
2</th>
cell 1</td>
cell 2</td>
cell 1</td>
cell 2</td>
This will result in a table that looks a little like this in a web browser;
Header 1
Header 2
row 1, cell 1
row 2, cell 1
row 1, cell 2
row 2, cell 2
The entire table itself is enclosed in <table> tags. Each row is enclosed in <tr> tags. Each row
has two items which equate to the two columns. Each piece of data for each cell is enclosed in
a <td> tag except for the first row, which is a header and therefore has a special tag <th> that
denotes it as a header making it bold and centred. For the sake of ease of viewing we have told
the table to place a border around each cell and we do this in the first <table> tag with the
border="1" statement (although in this book view it may be absent).
http://prcweb.co.uk/lab/selection/
184
The good news is that you dont need to fully understand all this, but it will help with
the explanation of what were doing in the code below.
There are three main things you need to do to the basic line graph to get your table to display.
1. Add some CSS
2. Add some table building d3.js code
3. Make a small but cunning change
This sets a padding of 1 px around each cell and 4 px between each column.
Feel free to play with the figures to suit your application, Ive just set them there because
I thought they looked appropriate.
Ive placed this portion of CSS at the end of our <style> section.
185
And we should take care to add it into the code at the end of the portion where weve finished
drawing the graph, but before the enclosing curly and regular brackets that complete the portion
of the graph that has loaded our data.tsv file. This is because we want our new piece of code
to have access to that data and if we place it after those brackets it wont know what data to
display.
So, right about here;
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// <= Add the code right here!
});
Now, were going to break with tradition a bit here and examine what our current state of code
produces. Then were going to explain something different. THEN were going to come back and
explain the code
Check it out
186
187
How to do it?
Its actually remarkably easy. Just change the following lines in the basic line graph code to
amend date to date1 and youre good to go.
.x(function(d) { return x(d.date1); })
d.date1 = parseDate(d.date);
The middle line is probably the most significant, since this is the point where we declare date1,
assign a time format and bring a new column of data into being. The others simply refer to the
data.
So well make those small changes and now we can return to explain the d3.js code
This portion simply calls the tabulate function using the date and close columns of our data
array. Simply add or remove whichever columns you want to appear in your table (so long as
they are in your data.tsv file) and they will be in your table. The tabulate function makes up all
of the other part of the added code. So we come to the first block of the tabulate function;
function tabulate(data, columns) {
var table = d3.select("body").append("table")
.attr("style", "margin-left: 250px"),
thead = table.append("thead"),
tbody = table.append("tbody");
Here the tabulate function is declared (function tabulate) and the variables that the function
will be using are specified ((data, columns)). In our case data is of course our data array and
columns refers to ["date", "close"].
The next line appends the table to the body of the web page (so it will occur just under the graph
in this case). The I do something just slightly sneaky. The line .attr("style", "margin-left:
250px"), is actually not the code that was used to produce the table with the huge date/ time
188
formatted info on. I deliberately used .attr("style", "margin-left: 0px"), for the huge date
/ time table since its job is to indent the table by a specified amount from the left hand side of
the page. And since the huge date time values would have pushed the table severely to the right,
I cheated and used 0 instead of 250. For the purposes of the final example where the date / time
values are formatted as expected, 250 is a good value.
The next two lines declare the functions we will use to add in the header cells (since they use
the <th> tags for content) and the cells for the main body of the table (they use <td>).
The next block of code adds in the header row;
thead.append("tr")
.selectAll("th")
.data(columns)
.enter()
.append("th")
.text(function(column) { return column; });
Here we first append a row tag (<tr>), then we gather all the columns that we have in our function
(remember they were ["date", "close"] and add them to our row using header tags (<th>).
The next block of code assigns the row variable to return (append) a row tag (<tr>) whenever
its called
var rows = tbody.selectAll("tr")
.data(data)
.enter()
.append("tr");
where we select each row that weve added (var cells = rows.selectAll("td")). Then the
following five lines works out from the intersection of the row and column which piece of data
were looking at for each cell.
Then the last four lines take that piece of data (d.value) and wrap it in table data tags (<td>)
and place it in the correct cell as HTML.
Its a very neat piece of code and I struggle to get my head around it, but that doesnt mean that
I cant appreciate the cleverness of it :-).
189
Wrap up
So there we have it. Hopefully enough to explain what is going on and perhaps also enough to
convince ourselves that D3 is indeed more than just pretty pictures. Its all about the Data Driven
Documents.
This file has been saved as table-plus-graph.html and has been added into the downloads section
on d3noob.org with the general examples files.
190
This makes the assumption that you still have the data2.tsv file in place. If not, rush
away and get it from d3noob.orgs downloads page.
From here (and as promised in the previous chapter), its just a matter of adding in the extra
column you want (in this case its the open column) like so;
var peopleTable = tabulate(data, ["date", "close", "open"]);
191
Yes, if youre wondering, I have cheated slightly and changed the table indent to make
it look slightly prettier.
So can we go further?
You know we can
In the section where we get our data and format it, lets add another column to our array in the
form of a difference between the close value and the open value (and well call it diff).
d3.tsv("data/data2.tsv", function(error, data) {
data.forEach(function(d) {
d.date1 = parseDate(d.date);
d.close = +d.close;
d.open = +d.open; // <= added this for tidy house keeping
d.diff = Math.round(( d.close - d.open ) * 100 ) / 100;
});
(the Math.round function is to make sure we get a reasonable figure to display, otherwise it tends
to get carried away with decimal places)
So now we add in our new column (diff) to be tabulated;
var peopleTable = tabulate(data, ["date", "close", "open", "diff"]);
And yes, I changed the table indent again. I am a serial offender and will continue to
change it to suit.
Sorting on a column
So now with our four columns of awesome data, it turns out that were really interested in the
ones that have the highest close values. So we can sort on the close column by adding the
following lines directly after the line where we declare the peopleTable function (which I will
include in the code snipped below for reference).
192
This is quite a tidy little piece of script. You can see it selecting the headers (selectAll("thead
th")), then the first character in each header (column.charAt(0)), changing it to upper-case
(.toUpperCase()) and adding it back to the rest of the string (+ column.substr(1)).
With the ultimate result
193
Add borders
Sure our table looks nice and neatly arranged, but would a border look better?
Well, heres one way to do it;
All we need to do is add a border style to our table by adding in this line here;
function tabulate(data, columns) {
var table = d3.select("body").append("table")
.attr("style", "margin-left: 200px") // <= Remove the comma
.style("border", "2px black solid"), // <= Add this line in
thead = table.append("thead"),
tbody = table.append("tbody");
(dont forget to move the comma from the end of the margin-left line)
And the result is a tidy black border.
194
td, th {
padding: 1px 4px;
border: 1px black solid;
}
So now each cell has a slightly more subtle border like this;
Yikes! Not quite as subtle as I would have expected. I suppose its another example of the code
actually doing what you asked it to do. No problem, border-collapse to the rescue. Add the
following line into here;
function tabulate(data, columns) {
var table = d3.select("body").append("table")
.attr("style", "margin-left: 200px")
.style("border-collapse", "collapse")
.style("border", "2px black solid"),
thead = table.append("thead"),
tbody = table.append("tbody");
195
The border-collapse style tells the table to overlap each cells borders, rather than
treat them as discrete entities. So in this case it looks a bit better.
This file has been saved as table-plus-addins.html and has been added into the downloads section
on d3noob.org with the general examples files.
196
(Notice the little pointing finger at the bottom that would indicate that there actually is a link
there.)
The code that we will use as a starting point is this simple example that draws a green rectangle
and overlays some text on it;
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 449;
var height = 249;
var word = "gongoozler";
var holder = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// draw a rectangle
holder.append("rect")
.attr("x", 100)
.attr("y", 50)
197
.attr("height", 100)
.attr("width", 200)
.style("fill", "lightgreen")
.attr("rx", 10)
.attr("ry", 10);
// draw text on the screen
holder.append("text")
.attr("x", 200)
.attr("y", 100)
.style("fill", "black")
.style("font-size", "20px")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(word);
</script>
</body>
Theres nothing too spectacular about the file. Theres a little bit of styling and tweaking of
attributes, but nothing too extreme. The only slightly odd part would be defining the word
that is printed out as a variable (var word = "gongoozler";) and then adding it as a variable
(.text(word);) instead of just putting the word directly in there (which we could do like this
.text("gongoozler");). Were going to do this deliberately to explore additional options for
making our links a little more dynamic.
http://www.w3schools.com/html/html_links.asp
198
holder.append("a")
.attr("xlink:href", "http://en.wikipedia.org")
.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("height", 100)
.attr("width", 200)
.style("fill", "lightgreen")
.attr("rx", 10)
.attr("ry", 10);
Its important to append the link before the object (otherwise it wont work) but other than that,
its a pretty simple job.
The only fly in the ointment is that while we now have a rectangle that links to Wikipedia, if we
hover our mouse over the text, we lose our link (since we havent told the text to link anywhere).
We can remedy that by doing exactly the same thing with the text element;
holder.append("a")
.attr("xlink:href", "http://en.wikipedia.org/wiki/"+word)
.append("text")
.attr("x", 200)
.attr("y", 100)
.style("fill", "black")
.style("font-size", "20px")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(word);
The only slight difference here is that we have used the address for Wikipedia as our base and
added the variable for our word to the end of it so that the resulting web address takes us to
Wikipedia and the specific page for the word gongoozler. Hopefully this will indicate that if we
had a set of variables in an array we would make our links a little more dynamic.
199
holder.append("text")
.attr("x", 200)
.attr("y", 100)
.style("fill", "black")
.style("font-size", "20px")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("pointer-events", "none")
.text(word);
And as you can see from the image below, the pointer will happily ignore the text while reading
the link from the rectangle.
The complete code for this example is available in the appendices and a live version can be found
on bl.ocks.org and GitHub.
http://bl.ocks.org/d3noob/8150631
https://gist.github.com/d3noob/8150631
200
In the following steps well go through a process that (hopefully) demonstrates that we can
transform identifiers that would represent the closing price for a stock of 58.3 on 2013-03-14 into
more traditional x,y coordinates.
I think of data as having an identifier and a value.
identifier: value
If a point on a graph is located at the x,y coordinates 150,25 then the identifier x has a value
150.
"x": 150
If the x axis was a time-line, the true value for x could be 2013-03-14.
http://www.json.org/
201
"x": "2013-03-14"
This example might look similar to those seen by users of d3.js, since if were using date / time
format we can let D3 sort out the messy parts like what coordinates to provide for the screen.
And theres no reason why we couldnt give the x identifier a more human readable label such
as date. So our data would look like;
"date": "2013-03-14"
This is only one part of our original x,y = 150,25 data set. The same way that the x value
represented a position on the x axis that was really a date, the y value represents a position
on the y axis that is really another number. It only gets converted to 25 when we need to plot
a position on a graph at 150,25. If the y component represents the closing price of a stock we
could take the same principles used to transform
"x": 150
into
"date": "2013-03-14"
to change .
"y": 25
into
"close": 58.3
This might sound slightly confusing, so try to think of it this way. We want to plot a point on a
graph at 150,25, but the data that this position is derived from is really 2013-03-14, 58.3. D3 can
look after all the scaling and determination of the range so that the point gets plotted at 150,25
and our originating data can now be represented as;
"date": "2013-03-14", "close": 58.3
This represents two separate pieces of data. Each of which has an identifier (date or close)
and a value (2013-03-14 and 58.3)
If we wanted to have a series of these data points that represented several days of closing prices,
we would store them as an array of identifiers and values similar to this;
202
{
{
{
{
{
"date":
"date":
"date":
"date":
"date":
"2013-03-14",
"2013-03-15",
"2013-03-16",
"2013-03-17",
"2013-03-18",
close:
close:
close:
close:
close:
58.13
53.98
67.00
89.70
99.00
},
},
},
},
}
Each of the individual elements of the array is enclosed in curly brackets and separated by
commas.
I am making the assumption that you are familiar with the concept of what an array
is. If this is an unfamiliar word, in the context of data, then I strongly recommend that
you do some Goggling to build up some familiarity with the principle.
Now that we have an array, we can apply the same rules to it as we did the the item that had a
single value. We can give it an identifier all of its own. In this case we will call it data. Now we
can use our identifier: value analogy to use data as the identifier and the array as the value.
{ "data": [
{ "date":
{ "date":
{ "date":
{ "date":
{ "date":
] }
"2013-03-14",
"2013-03-15",
"2013-03-16",
"2013-03-17",
"2013-03-18",
close:
close:
close:
close:
close:
58.13
53.98
67.00
89.70
99.00
},
},
},
},
}
The array has been enclosed in square brackets to designate it as an array and the entire identifier:
value sequence has been encapsulated with curly braces (much the same way that the subset
date, close values were enclosed with curly braces.
If we try to convey the same principle in a more graphical format, we could show our initial
identifier and value for the x component like so;
203
More complex JSON files will have multiple levels of identifiers and values arranged in complex
hierarchies which can be difficult to interpret. However, laying out the data in a logical way in
a text file is an excellent way to start to make sense of the data.
204
What youre seeing here is an area where you can place your entire HTML code. So lets replace
the 11 lines of the place holder code with the simple graph example (just copy and paste it in
there over the top of the current 11 lines);
Now, there are two important things we have to do before it will work.
1. We need to tell he script where to find d3.js
2. We need to make our data accessible
Helping the script find d3.js is nice and easy. Just replace this line in your plunk;
http://plnkr.co/
http://plnkr.co/edit/
205
That will allow your plunk to use the version of d3.js that is hosted on d3js.org (it uses the
minimised version (which is why it has the min in it), but never fear, its still d3, just distilled
to enhance the flavour :-)).
Making our data available is only slightly more difficult.
In experimenting with Plunker, I found that there appears to be something odd about accessing
the tab separated values that we have been using thus far (in the data.tsv file), however, D3 to
the rescue! We can simply use Comma Separated Values (csv) instead.
So in preparation for this exercise, please edit your data.tsv file to have the tabs separating the
values replaced by commas and rename it data.csv.
We will host our data.csv file on plunker as well and there is built in functionality to let us do it.
In the top left hand corner, beside the FILES note, there is a +NEW section. Clicking on this
will allow you to create another file that will exist with your plunk for its use, so lets do that.
This will open a dialogue box that will ask you to name your new file.
206
This now shows us a blank file called data.csv, so now open up your data.csv file in whatever
editor youre using (I dont think a spreadsheet program is going to be a good idea since I doubt
that it will maintain the information in a textual form as were wanting it to do. So its Geany
for me). Copy the contents of your local data.csv file and paste it into the new plunker data.csv
file.
So now we have our data in there we need to tell our JavaScript where it is. So go back to the
index.html file (which is our simple graph code) and edit the line which finds the data.tsv file
from this
d3.tsv("data/data.tsv", function(error, data) {
to this
d3.csv("data.csv", function(error, data) {
Because were using relative addressing, and plunker stores the files for the graphing script and
the data side by side, we just removed the portion of the address that told our original code to
look in the data directory and told it to look in the current directory. And that should be that!
Now if you look on the right hand side of the screen, there is a little eye icon. If you click on it,
it opens up a preview window of your file in action and viola!
If the graph doesnt appear, go through the steps outlined above and just check that the edits
are made correctly. Unfortunately I havent found a nice mechanism for troubleshooting inside
Plunker yet (not like using F12 on Chrome).
But wait! Theres more!
207
If you now click on the Save button at the top of the screen, you will get some new button
options.
One of them is the orange one for showing off your work.
If you click on this, it will present you with several different options.
The first one is a link that will give others the option to collaborate on the script.
The second is a link that will allow others to preview the work; http://embed.plnkr.co/QSCkG8Rf2qFgrCqq7Vfn
The last will allow you to embed your graph in a separate web page somewhere. Which Ive
tested with blogger and seems to work really well! (see image below).
http://embed.plnkr.co/QSCkG8Rf2qFgrCqq7Vfn
208
So, Im impressed, Nice work by Plunker and its creator Geoff Goodman.
209
Manipulating data
How to use data imported from a csv file with spaces in the
header.
When importing data from a csv file that has headers with spaces in the middle of some of the
fields there is a need to address the data slightly differently in order for it to be used easily in
your JavaScript.
For example the following csv data has a column named Date Purchased;
Value,Date Purchased,Score
12345,2011-03-23,99
22345,2011-03-24,100
32345,2011-03-25,99
42345,2011-03-26,100
This is not an uncommon occurrence since RFC 4180 which specifies csv content allows for it
and d3.js supports the RFC;
Within the header and each record, there may be one or more fields, separated by
commas. Each line should contain the same number of fields throughout the file.
Spaces are considered part of a field and should not be ignored.
When we go to import the data using the d3.csv function, we need to reference the Data
Purchased column in a way that makes allowances for the space. The following piece of script
(with grateful thanks to Stephen Thomas for answering my Stack Overflow question) appears
to be the most basic solution.
d3.csv("sample-data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d['Date Purchased']);
});
...
});
In the example above the Date Purchased column is re-declared as date making working in
the following script much easier.
http://tools.ietf.org/html/rfc4180
http://stackoverflow.com/questions/21715201/unable-to-reference-d3-js-data-imported-from-a-csv-file-with-spaces-in-the-heade
210
We can use the JavaScript substring() method to easily remove the leading character from the
data.
The following example processes our csv file after loading it and for each value entry on each
row takes a substring of the entry that removes the first character and retains the rest.
d3.csv("sample-data.csv", function(error, data) {
data.forEach(function(d) {
d.value = +d.value.substring(1);
});
...
});
The substring() function includes a start index (as used above) and optionally a stop index.
More on how these can be configured can be found on the w3schools site.
http://www.w3schools.com/jsref/jsref_substring.asp
211
We will nest the data according to the date and sum the data for each date so that our data is in
the equivalent form of;
key,values
2011-03-23,5
2011-03-24,21
2011-03-25,14
We are assuming the data is in the form of our initial csv file and is named source-data.csv.
The first thing we do is load that file and assign the loaded array the variable name csv_data.
https://github.com/mbostock/d3/wiki/Arrays#wiki--nest
212
Then we declare our new arrays name will be data and we initiate the nest function;
var data = d3.nest()
We assign the key for our new array as date. A key is like a way of saying This is the thing we
will be grouping on. In other words our resultant array will have a single entry for each unique
date value.
.key(function(d) { return d.date;})
Then we include the rollup function that takes all the individual value variables that are in each
unique date field and sums them;
.rollup(function(d) {
return d3.sum(d, function(g) {return g.value; });
Lastly we tell the entire nest function which data array we will be using for our source of data.
}).entries(csv_data);
What if your data turns out to be unsorted? Never fear, we can easily sort on the key value by
tacking on the sortKeys function like so;
.key(function(d) { return d.date;}).sortKeys(d3.ascending)
You should note that our data will have changed name from date and value. This is as a function
of the nest and rollup process. But never fear, its a simple task to re-name them if necessary
using the following function (which could include a call to parse the date, but I have omitted it
for clarity);
data.forEach(function(d) {
d.date = d.key;
d.value = d.values;
});
Bar Charts
A bar chart is a visual representation using either horizontal or vertical bars to show comparisons
between discrete categories. There are a number of variations of bar charts including stacked,
grouped, horizontal and vertical.
There is a wealth of examples of bar charts on the web, but I would recommend a visit to the
D3.js gallery maintained by Christophe Viau as a starting point to get some ideas.
We will work through a simple vertical bar chart that uses a value on the y axis and date values
on the x axis.
The end result will look like this;
Bar chart
The data
The data for this example will be sourced from an external csv file named bar-data.csv. It
consists of a column of dates in year-month format and its contents are as follows;
date,value
2013-01,53
2013-02,165
2013-03,269
2013-04,344
2013-05,376
2013-06,410
2013-07,421
2013-08,405
http://christopheviau.com/d3list/gallery.html#visualizationType=bar
214
Bar Charts
2013-09,376
2013-10,359
2013-11,392
2013-12,433
2014-01,455
2014-02,478
The code
The full code listing for the example we are going to work through is as follows;
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var
parseDate = d3.time.format("%Y-%m").parse;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
Bar Charts
215
216
Bar Charts
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
</script>
</body>
The full code for this example can be found on github, in the appendices of this book
or in the code samples bundled with this book (bar.html and bar-data.csv). A working
example can be found on bl.ocks.org.
https://gist.github.com/d3noob/8952219
https://leanpub.com/D3-Tips-and-Tricks
http://bl.ocks.org/d3noob/8952219
217
Bar Charts
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
Then our JavaScript section starts and the first thing that happens is that we set the size of the
area that were going to use for the chart and the margins;
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
The next section of our code includes some of the functions that will be called from the main
body of the code.
We have a familiar parseDate function with a slight twist. Since our source data for the date is
made up of only the year and month, these are the only two portions of the date that need to be
recognised;
var
parseDate = d3.time.format("%Y-%m").parse;
The next section declares the function to determine positioning in the x domain.
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
The ordinal scale is used to describe a range of discrete values. In our case they are a set of
monthly values. The rangeRoundBands operator provides the magic that arranges our bars in a
graceful way across the x axis. In our example we use it to set the range that our bars will cover
(in this case from 0 to the width of the graph) and the amount of padding between the bars (in
this case we have selected .05 which equates to approximately (depending on the number of
pixels available) 5% of the bar width.
The function to set the scaling in the y domain is the same as most of our other graph examples;
var y = d3.scale.linear().range([height, 0]);
The declarations for our two axes are relatively simple, with the only exception being to force
the format of the labels for the x axis into a year-month format.
Bar Charts
218
The next block of code selects the body on the web page and appends an svg object to it of the
size that we have set up with our width, height and margins.
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
It also adds a g element that provides a reference point for adding our axes.
Then we begin the main body of our JavaScript. We load our csv file and then loop through it
making sure that the dates and numerical values are recognised correctly;
d3.csv("bar-data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
We then then work through our x and y data and ensure that it is scaled to the domains we are
working in;
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
Bar Charts
219
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
This block of code creates the bars (selectAll("bar")) and associates each of them with a data
set (.data(data)).
We then append a rectangle (.append("rect")) with values for x/y position and height/width
as configured in our earlier code.
The end result is our pretty looking bar chart;
220
Bar Charts
Bar chart
Sankey Diagrams
What is a Sankey Diagram?
A Sankey diagram is a type of flow diagram where the flow is represented by arrows of varying
thickness depending on the quantity of flow.
They are often used to visualize energy, material or cost transfers and are especially useful in
demonstrating proportionality to a flow where different parts of the diagram represent different
quantities in a system.
Probably the most famous example of a Sankey diagram is Charles Minards Map of Napoleons
Russian Campaign of 1812.
From Wikipedia;
tienne-Jules Marey first called notice to this dramatic depiction of the fate of Napoleons army
in the Russian campaign, saying it defies the pen of the historian in its brutal eloquence. Edward
Tufte says it may well be the best statistical graphic ever drawn and uses it as a prime example
in The Visual Display of Quantitative Information.
Wikipedia has a great explanation of the diagram type and there is a wealth of information
dedicated to it on the inter-web. I heartily recommend http://www.sankey-diagrams.com/ for all
things Sankey!
So it would come as little surprise that Mike Bostock has developed a plugin for Sankey diagrams
(http://bost.ocks.org/mike/sankey/) so that we can all enjoy Sankey goodness with lashings of D3.
http://en.wikipedia.org/wiki/Sankey_diagram
222
Sankey Diagrams
the data that generates them must be formatted as nodes and links as well.
For instance a JSON file with appropriate data to build the diagram above could look like the
following;
{
"nodes":[
{"node":0,"name":"node0"},
{"node":1,"name":"node1"},
{"node":2,"name":"node2"},
{"node":3,"name":"node3"},
{"node":4,"name":"node4"}
],
"links":[
{"source":0,"target":2,"value":2},
{"source":1,"target":2,"value":2},
{"source":1,"target":3,"value":2},
{"source":0,"target":4,"value":2},
{"source":2,"target":3,"value":2},
{"source":2,"target":4,"value":2},
{"source":3,"target":4,"value":4}
]}
In the file above we have 6 nodes (0-5) sequentially numbered and with names appropriate to
their position in the list.
The sequential numbering is only for the purpose of highlighting the structure of the data, since
when we get D3 running, it will automatically index each of the nodes according to its position.
Sankey Diagrams
223
In other words, we could have omitted the node:n parts since D3 will know where each node
is anyway. The big deal is that WE need to know what each node is as well especially if were
going to be building the data by hand (doing it dynamically would be cool, but lets not get ahead
of ourselves just yet).
The links part of the data can be broken down into individual source to target links that have
an associated value (could be a quantity or strength, but at least a numeric value).
The source and target numbers are references to the list of nodes. So, source:1, target:2
means that this link is whatever node appears at position 1 going to whatever node appears at
position 2. The important point to make here is that D3 will not be interested in the numerical
value of the node, just its position in the list (starting at zero).
http://bost.ocks.org/mike/sankey/
Sankey Diagrams
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<p id="chart">
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/sankey.js"></script>
<script>
224
Sankey Diagrams
225
Sankey Diagrams
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) {
return d.name + "\n" + format(d.value); });
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
)
+ "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
});
So, going straight to the style sheet bounded by the <style> tags;
226
Sankey Diagrams
227
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
The CSS in this example is mainly concerned with formatting of the mouse cursor as it moves
around the diagram.
The first part
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
provides the properties for the node rectangles. It changes the icon for the cursor when it
moves over the rectangle to one that looks like it will move the rectangle (there is a range of
different icons that can be defined here http://www.echoecho.com/csscursors.htm), sets the fill
colour to mostly opaque and keeps the edges sharp.
The next block
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
sets the properties for the text at each node. The mouse is told to essentially ignore the text in
favour of anything thats under it (in the case of moving or highlighting something else) and a
slight shadow is applied for readability).
The following block
Sankey Diagrams
228
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
makes sure that the link has no fill (it actually appears to be a bendy rectangle with very thick
edges that make the element appear to be a solid block), colours the edges black (#000) and makes
the edges almost transparent.
The last block.
.link:hover {
stroke-opacity: .5;
}
simply changes the opacity of the link when the mouse goes over it so that its more visible.
If so desired, we could change the colour of the highlighted link by adding in a line to this block
changing the colour like this stroke: red;.
Just before we get into the JavaScript, we do something a little different for d3.js. We tells it to
use a plug-in with the followig line;
<script src="js/sankey.js"></script>
The concept of a plug-in is that it is a separate piece of code that will allow additional
functionality to a core block (which in this case is d3.js). There are a range of plug-ins available
and we will need to source the sankey.js file from the repository and place that somewhere
where our HTML code can access it. In this case I have put it in the js directory that resides in
the root directory of the web page.
The start of our JavaScript begins by defining a range of variables that well be using.
Our units are set as Widgets (var units = "Widgets";), which is just a convenient generic
(nonsense) term to provide the impression that the flow of items in this case is widgets being
passed from one person to another.
We then set our canvas size and margins
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 700 - margin.left margin.right,
height = 300 - margin.top margin.bottom;
https://github.com/d3/d3-plugins
Sankey Diagrams
229
The formatNumber function acts on a number to set it to zero decimal places in this case. In the
original Mike Bostock example it was to three places, but for widgets Im presuming we dont
divide :-).
format is a function that returns a given number formatted with formatNumber as well as a space
and our units of choice (Widgets). This is used to display the values for the links and nodes later
in the script.
The color = d3.scale.category20(); line is really interesting and provides access to a colour
scale that is pre-defined for your convenience! Later in the code we will see it in action.
Our next block of code positions our canvas onto our page in relation to the size and margins
we have already defined;
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
Without trying to state the obvious, this sets the width of the nodes (.nodeWidth(36)), the
padding between the nodes (.nodePadding(40)) and the size of the diagram(.size([width,
height]);).
The following line defines the path variable as a pointer to the sankey function that makes the
links between the nodes do their clever thing of bending into the right places;
var path = sankey.link();
I make the presumption that this is a defined function within sankey.js. Then we load the data
for our sankey diagram with the following line;
https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-category10
Sankey Diagrams
230
As we have seen in previous usage of the d3.json, d3.csv and d3.tsv functions, this is a wrapper
that acts on all the code within it bringing the data in the form of graph to the remaining code.
I think its a good time to take a slightly closer look at the data that well be using;
{
"nodes":[
{"node":0,"name":"node0"},
{"node":1,"name":"node1"},
{"node":2,"name":"node2"},
{"node":3,"name":"node3"},
{"node":4,"name":"node4"}
],
"links":[
{"source":0,"target":2,"value":2},
{"source":1,"target":2,"value":2},
{"source":1,"target":3,"value":2},
{"source":0,"target":4,"value":2},
{"source":2,"target":3,"value":2},
{"source":2,"target":4,"value":2},
{"source":3,"target":4,"value":4}
]}
I want to look at the data now, because it highlights how it is accessed throughout this portion of
the code. It is split into two different blocks, nodes and links. The subset of variables available
under nodes is node and name. Likewise under links we have source, target and value.
This means that when we want to act on a subset of our data we define which piece by defining
the hierarchy that leads to it. For instance, if we want to define an action for all the links, we
would use graph.links (theyre kind of chained together).
Let me take this opportunity to apologise to all those programmers who actually know
exactly what is going on here. Its a mystery to me, but this is how I like to tell myself
it works to help me get by :-).
Now that we have our data loaded, we can assign the data to the sankey function so that it knows
how to deal with it behind the scenes;
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
231
Sankey Diagrams
In keeping with our previous description of whats going on with the data, we have told the
sankey function that the nodes it will be dealing with are in graph.nodes of our data structure.
Im not sure what the .layout(32); portion of the code does, but Id be interested to hear
from any more knowledgeable readers. Ive tried changing the values to no apparent effect and
googling has drawn a blank. Internally to the sankey.js file it seems to indicate iterations while
it establishes computeNodeLinks, computeNodeValues, computeNodeBreadths, computeNodeDepths (iterations) and computeLinkDepths.
Then we add our links to the diagram with the following block of code;
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
This is an analogue of the block of code we examined way back in the section that we covered
in explaining the code of our first simple graph.
We append svg elements for our links based on the data in graph.links, then add in the paths
(using the appropriate CSS). We set the stroke width to the width of the value associated with
each link or 1. Whichever is the larger (by virtue of the Math.max function). As an interesting
sideline, if we force this value to 10 thusly
.style("stroke-width", 10)
Sankey Diagrams
232
I have to admit that I dont know what the sort line (.sort(function(a, b) { return b.dy a.dy; });) is supposed to achieve. Again, Id be interested to hear from any more knowledgeable
readers. Ive tried changing the values to no apparent effect.
The next block adds the titles to the links;
link.append("title")
.text(function(d) {
return d.source.name + " " +
d.target.name + "\n" + format(d.value); });
This code appends a text element to each link when moused over that contains the source and
target name (with a neat little arrow in between and the value) which, when applied with the
format function, adds the units.
The next block appends the node objects (but not the rectangles or text) and contains the
instructions to allow them to be arranged with the mouse.
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));
While it starts off in familiar territory with appending the node objects using the graph.nodes
data and putting them in the appropriate place with the transform attribute, I can only assume
that there is some trickery going on behind the scenes to make sure the mouse can do what it
needs to do with the d3.behaviour,drag function. There is some excellent documentation on
the wiki (https://github.com/mbostock/d3/wiki/Drag-behavior), but I can only presume that it
knows what its doing :-). The dragmove function is laid out at the end of the code, and we will
explain how that operates later.
I really enjoyed the next block;
Sankey Diagrams
233
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) {
return d.name + "\n" + format(d.value); });
It starts off with a fairly standard appending of a rectangle with a height generated by its value
{ return d.dy; } and a width dictated by the sankey.js file to fit the canvas (.attr("width",
sankey.nodeWidth())).
Then it gets interesting.
The colours are assigned in accordance with our earlier colour declaration and the individual
colours are added to the nodes by finding the first part of the name for each node and assigning
it a colour from the palate (the script looks for the first space in the name using a regular
expression). For instance: Widget X, Widget Y and Widget will all be coloured the same
even if the Widget X and Widget Y are inputs on the left and Widget is a node in the middle.
The stroke around the outside of the rectangle is then drawn in the same shade, but darker. Then
we return to the basics where we add the title of the node in a tool tip type effect along with the
value for the node.
From here we add the titles for the nodes;
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
Again, this looks pretty familiar. We position the text titles carefully to the left of the nodes. All
except for those affected by the filter function (return d.x < width / 2;). Where if the position
of the node on the x axis is less than half the width, the title is placed on the right of the node
and anchored at the start of the text. Very neat.
The last block is also pretty neat, and contains a little surprise for those who are so inclined.
234
Sankey Diagrams
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
This declares the function that controls the movement of the nodes with the mouse. It selects the
item that its operating over (d3.select(this)) and then allows translation in the y axis while
maintaining the link connection (sankey.relayout(); link.attr("d", path);).
But thats not the cool part. A quick look at the code should reveal that if you can move a node
in the y axis, there should be no reason why you cant move it in the x axis as well!
Sure enough, if you replace the code above with this
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
)
+ "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
Sankey Diagrams
235
I know it doesnt seem to add anything to the diagram (in fact, it could be argued that there is
a certain aspect of detraction) however, it doesnt mean that one day the idea doesnt come in
handy :-). You can see a live version on github.
As we also noted earlier, the node entries in the nodes section of the json file are superfluous
and are really only there for our benefit since D3 will automatically index the nodes starting at
zero. As a test to check this out we can change our data to the following;
{
"nodes":[
{"name":"Barry"},
{"name":"Frodo"},
{"name":"Elvis"},
{"name":"Sarah"},
{"name":"Alice"}
],
"links":[
{"source":0,"target":2,"value":2},
http://bl.ocks.org/d3noob/5028304
236
Sankey Diagrams
{"source":1,"target":2,"value":2},
{"source":1,"target":3,"value":2},
{"source":0,"target":4,"value":2},
{"source":2,"target":3,"value":2},
{"source":2,"target":4,"value":2},
{"source":3,"target":4,"value":4}
]}
(for reference this file is saved as sankey-formatted-names-and-numbers.json and the html file
is Sankey-formatted-names-and-numbers.html)
This will produce the following graph;
As you can see, essentially the same, but with easier to understand names.
As you can imagine, while the end result is great, the creation of the JSON file manually would
be painful at best. Doing something similar but with a greater number of nodes / links would be
a nightmare.
Lets see if we can make the process a bit easier and more flexible.
Sankey Diagrams
237
{
"nodes":[
{"name":"Barry"},
{"name":"Frodo"},
{"name":"Elvis"},
{"name":"Sarah"},
{"name":"Alice"}
],
"links":[
{"source":"Barry","target":"Elvis","value":2},
{"source":"Frodo","target":"Elvis","value":2},
{"source":"Frodo","target":"Sarah","value":2},
{"source":"Barry","target":"Alice","value":2},
{"source":"Elvis","target":"Sarah","value":2},
{"source":"Elvis","target":"Alice","value":2},
{"source":"Sarah","target":"Alice","value":4}
]}
This elegant solution comes from Stack Overflow and was provided by Chris Pettitt (nice job).
So if we sneak this piece of code into here
http://stackoverflow.com/questions/14629853/json-representation-for-d3-networks
238
Sankey Diagrams
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
and this time we use our JSON file with just names (sankey-formatted-names.json) and our
new html file (sankey-formatted-names.html) we find our Sankey diagram working perfectly!
the first thing it does is create an object called nodeMap (The difference between an array and
an object in JavaScript is one that is still a little blurry to me and judging from online comments,
I am not alone).
Then for each of the graph.node instances (where x is a range of numbers from 0 to the last
node), we assign each node name to a number.
Then in the next piece of code
Sankey Diagrams
239
graph.links = graph.links.map(function(x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
we go through all the links we have and for each link, we map the appropriate number to the
correct name.
Very clever.
We take this single line from our original Sankey diagram code;
d3.json("data/sankey-formatted.json", function(error, graph) {
http://bl.ocks.org/timelyportfolio/5052095
Sankey Diagrams
240
The comments in the code (and they are fuller in @timelyportfolios original gist solution)
explain the operation;
d3.csv("data/sankey.csv", function(error, data) {
Declares graph to consist of two empty arrays called nodes and links.
http://bl.ocks.org/timelyportfolio/5052095
Sankey Diagrams
241
data.forEach(function (d) {
graph.nodes.push({ "name": d.source });
graph.nodes.push({ "name": d.target });
graph.links.push({ "source": d.source,
"target": d.target,
"value": +d.value });
});
Takes the data loaded with the csv file and for each row loads variables for the source and
target into the nodes array. Then for each row it loads variables for the source target and
value into the links array.
graph.nodes = d3.keys(d3.nest()
.key(function (d) { return d.name; })
.map(graph.nodes));
Is a routine that Mike Bostock described on Google Groups that (as I understand it) nests
each node name as a key so that it returns with only unique nodes.
graph.links.forEach(function (d, i) {
graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
});
Goes through each link entry and, for each source and target, it finds the unique index
number of that name in the nodes array and assigns the link source and target an appropriate
number.
And finally
graph.nodes.forEach(function (d, i) {
graph.nodes[i] = { "name": d };
});
Goes through each node and (in the words of @timelyportfolio) make nodes an array of
objects rather than an array of strings (I dont really know what that means :-(. I just know it
works :-).)
There you have it. A Sankey diagram from a csv file. Well played @timelyportfolio!
Both the html file for the diagram (Sankey.formatted-csv.html) and the data file (sankey.csv)
can be found in the downloads section of d3noob.org.
https://groups.google.com/forum/#!msg/d3-js/pl297cFtIQk/Eso4q_eBu1IJ
Sankey Diagrams
242
First thing first, just as we did in the example on using MySQL, import your csv file into a MySQL
table which well call sankey1 in database homedb.
Now we want to write a query that pulls out all the DISTINCT names that appear it the source
and target columns. This will form our nodes portion of the JSON data.
SELECT DISTINCT(`source`) AS name FROM `sankey1`
UNION
SELECT DISTINCT(`target`) AS name FROM `sankey1`
GROUP BY name
This query actually mashes two separate queries together where each returns DISTINCT
instances of each source and target from the source and target columns. By default, the UNION
operator eliminates duplicate rows from the result which means we have a list of each node in
the table.
243
Sankey Diagrams
This query gets all the sources plus all the targets and groups them by first the source and then
the target. Each line is therefore unique and the COUNT(*) sums up the number of times that each
unique combination occurs.
244
Sankey Diagrams
<?php
$username = "homedbuser";
$password = "homedbuser";
$host = "localhost";
$database="homedb";
$server = mysql_connect($host, $username, $password);
$connection = mysql_select_db($database, $server);
$myquery = "
SELECT DISTINCT(`source`) AS name FROM `sankey1`
UNION
SELECT DISTINCT(`target`) AS name FROM `sankey1`
GROUP BY name
";
$query = mysql_query($myquery);
if ( ! $myquery ) {
echo mysql_error();
die;
}
$nodes = array();
for ($x = 0; $x < mysql_num_rows($query); $x++) {
$nodes[] = mysql_fetch_assoc($query);
}
$myquery = "
SELECT `source` AS source, `target`
FROM `sankey1`
GROUP BY source, target
";
$query = mysql_query($myquery);
if ( ! $myquery ) {
echo mysql_error();
die;
}
$links = array();
for ($x = 0; $x < mysql_num_rows($query); $x++) {
$links[] = mysql_fetch_assoc($query);
}
Sankey Diagrams
echo
echo
echo
echo
245
"{";
'"links": ', json_encode($links), "\n";
',"nodes": ', json_encode($nodes), "\n";
"}";
mysql_close($server);
?>
Astute readers will recognise that this is very similar to the script that we used to extract data
from the MySQL database for generating a simple line graph. If you havent checked it out, and
youre unfamiliar with PHP, you will want to read that section first.
We declare all the appropriate variables which we will use to connect to the database. We then
connect to the database and run our query.
After that we store the nodes data in an array called $nodes.
Then we run our second query (we dont close the connection to the database since were not
finished with it yet).
The second query returns the link results into a second array called $links (pretty imaginative).
Now we come to a part thats a bit different. We still need to echo out the data in the same way
we did in our line graph, but in this case we need to add the data together with the associated
links and nodes identifiers.
echo
echo
echo
echo
"{";
'"links": ', json_encode($links), "\n";
',"nodes": ', json_encode($nodes), "\n";
"}";
(if you look closely, the syntax will produce our JSON formatted output).
At last, we need to call this PHP script from our html file in the same way that we did for the
line graph. So amend the html file to change the loading of the JSON data to be from our PHP
file thusly;
d3.json("php/sankey.php", function(error, graph) {
And there you have it! So many ways to get the data.
Both the PHP file (sankey.php) and the html file (sankey-mysql-import.html) are available in the
downloads section on d3noob.org.
Sankey Diagrams
246
Well, I suppose it all depends on your data set, but remember, Sankey diagrams are good at flows,
but they wont do loops / cycles easily (although there has been some good work done in this direction here http://bl.ocks.org/cfergus/3956043 and here http://bl.ocks.org/kunalb/4658510).
So lets choose a flow.
In this case well selected the flow of data that represents a view of global, anthropogenic greenhouse gas (GHG) emissions. The image is an alternative to the excellent diagram on the World
Resources Institute (http://www.wri.org/chart/world-greenhouse-gas-emissions-2005) and as
such my version pales in comparison to theirs.
However, the aim is to play with the technique, not to emulate :-).
So starting with the data presented in the original diagram, we have to capture the links into
a csv file. I did this the hard way (since there didnt appear to be an electronic version of the
data) by reading the graph and entering the figures into a csv file. From here we import it into
our MySQL database and then convert it into sankey formatted JSON by using our PHP script
that we played with in the example of extracting information from a MySQL database. In this
case, instead of needing to perform a COUNT(*) on the data, its slightly easier since the value is
already present.
Because we want this diagram to be hosted on Gist and accessible on bl.ocks.org, we run
the PHP file directly into the browser so that it just shows the JSON data on the screen.
We save this file with the suffix .json and we have our data (in this case the file is named
sankeygreenhouse.json).
We amend our html file to look at our new .json file and voila!
http://bl.ocks.org/cfergus/3956043
http://bl.ocks.org/kunalb/4658510
247
Sankey Diagrams
Sankeytastic!
You can find this as a live example and with all the code and data on bl.ocks.org.
http://bl.ocks.org/d3noob/5015397
Tree Diagrams
What is a Tree Diagram?
The Tree layout is not a distinct type of diagram per se. Instead, its representative of D3s
family of hierarchical layouts.
Its designed to produce a node-link diagram that lays out the connection between nodes in a
method that displays the relationship of one node to another in a parent-child fashion.
For example, the following diagram shows a root node (the starting position) labelled Top Node
which has two children (Bob: Child of Top Node and Sally: Child of Top Node). Subsequently,
Bob:Child of Top Node has two dependant nodes (children) Son of Bob and Daughter of Bob.
The clear advantage to this style of diagram is that describing it in text is difficult, but
representing it graphically makes the relationships easy to determine.
The data required to produce this type of layout needs to describe the relationships, but this
is not necessarily an onerous task. For example, the following is the data (in JSON form) for
the diagram above and it shows the minimum information required to form the correct layout
hierarchy.
https://github.com/mbostock/d3/wiki/Tree-Layout
249
Tree Diagrams
{
"name": "Top Node",
"children": [
{
"name": "Bob: Child of Top Node",
"parent": "Top Node",
"children": [
{
"name": "Son of Bob",
"parent": "Bob: Child of Top Node"
},
{
"name": "Daughter of Bob",
"parent": "Bob: Child of Top Node"
}
]
},
{
"name": "Sally: Child of Top Node",
"parent": "Top Node"
}
]
}
It shows each node as having a name that identifies it on the tree and, where appropriate, the
children it has (as an array) and its parent.
The data shown above is arranged as a hierarchy and it is not always possible to source
data that is organised so nicely. As we go through use examples for this type of diagram
we will look at options for importing flat data and converting it into a hierarchical
form.
There is a wealth of examples of tree diagrams on the web, but I would recommend a visit to the
D3.js gallery maintained by Christophe Viau as a starting point to get some ideas.
In this chapter were going to look at a very simple piece of code to generate a tree diagram before
looking at different ways to adapt it. Including rotating it to be vertical, adding some dynamic
styling to the nodes, importing from a flat file and from an external source. Finally well look at
a more complex example that is more commonly used on the web that allows a user to expand
and collapse nodes interactively.
http://christopheviau.com/d3list/gallery.html#visualizationType=tree
250
Tree Diagrams
Tree Diagrams
</style>
</head>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var treeData = [
{
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
];
// ************** Generate the tree diagram
*****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
251
Tree Diagrams
252
253
Tree Diagrams
.style("fill-opacity", 1);
// Declare the links
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
}
</script>
</body>
</html>
The full code for this example can be found on github, in the appendices of this book
or in the code samples bundled with this book (simple-tree-diagram.html). A working
example can be found on bl.ocks.org.
In the course of describing the operation of the file I will gloss over the aspects of the structure
of an HTML file which have already been described at the start of the book. Likewise, aspects of
the JavaScript functions that have already been covered will only be briefly explained.
The start of the file deals with setting up the documents head and body loading the d3.js script
and setting up the css in the <style> section.
The css section sets styling for the circle that represents the nodes, the text alongside them and
the links between them.
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
https://gist.github.com/d3noob/8323795
http://bl.ocks.org/d3noob/8323795
Tree Diagrams
254
Then our JavaScript section starts and the first thing that happens is that we declare our array
of data in the following code;
var treeData = [
{
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
];
As outlined at the start of the chapter, this data is encoded hierarchically in JavaScript Object
Notation (JSON). Each node must have a name and either a parent or child node(s) or both. There
are many examples of hierarchical data that can be encoded in this way. From the traditional
parent - offspring example to directories on a hard drive or a breakdown of materials for a
complex object. Any system of encoding where there is a single outcome from multiple sources
like an election or an alert encoding system dependent on multiple trigger points.
The next section of our code declares some of the standard features for our diagram such as the
size and shape of the svg container with margins included.
Tree Diagrams
255
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
It also assigns the variable / function tree to the d3.js function that is used to assign and
calculate the data required for the nodes and links for our diagram. We will be calling that later.
The next block of code declares the function that will be used to draw the links between the
nodes. This isnt the part of the code where the links are drawn, this is just declaring the
variable/function that will be used when it does happen.
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
This uses the d3.js diagonal function to help draw a path between two points such that the
line exhibits some nice flowing curves (cubic Bzier ) to make the connection.
The next block of code appends our SVG working area to the body of our web page and creates
a group elements (<g>) that will contain our svg objects (our nodes, text and links).
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
The next line is one that vexed me for a while and one that I think means there are other areas
of my code that could be improved (for a short interlude on why this tried me, feel free to catch
this question on Stack Overflow).
root = treeData[0];
It might not look like much and to those familiar with JavaScript, it will be a no-brainer, but
what the line is doing is defining what tree from our data is going to be used. Because our data
is an array, the first level of the array is treeData. The name of the first object on the first level
of treeData is Top Level. This (being the first object) is object 0. Therefore our starting point
is treeData[0]. We could confirm this by changing the declaration to
https://github.com/mbostock/d3/wiki/Tree-Layout
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-diagonal
http://stackoverflow.com/questions/20940854/how-to-load-data-from-an-internal-json-array-rather-than-from-an-external-resour
256
Tree Diagrams
root = treeData[0].children[0];
This will take the root point for our diagram as being the first child (child[0]) of the the first
level of treeData. As a result, our tree diagram will look like this
This calls the function update and uses the data root to create our tree.
The last significant part of the code is the function update. This is the part of the code that pulls
together the functions and data that we have declared and draws our tree.
The first step in that process is to assign our nodes and links.
var nodes = tree.nodes(root),
links = tree.links(nodes);
This uses our previously declared tree function to work its d3.js magic on our data (root) and
to determine the node details and from the node details we can determine the link details.
If youre wondering how this all works, Im afraid that I wont be able to help much, but a starting
point would be the results that the process produces which is a set of nodes, each of which has a
set of characteristics. Those characteristics are; - .children: Which is an array of any children
that exist for that node. - .depth: Which is the depth (described in a few paragraphs time). - .id:
Which is a unique number identifier for each node. - .name: The name we have assigned from
our data. - .parent: The name of the parent of the node. - .x and .y: Which are the x and y
positions on the screen of the node.
From this node data a set of links joining the nodes is created. Each link consists of a .source
and .target. Each of which is a node.
We then determine the horizontal spacing of the nodes.
257
Tree Diagrams
This uses the depth of the node (as determined for each node in the nodes = tree.nodes(root)
line) to calculate the position on the y axis of the screen.
The depth refers to the position in the tree relative to the root node on the left. The following
picture shows how the depth relates to the position of the node in the tree.
So by adjusting our expansion factor (currently set to 180) we can adjust the spacing of the
nodes. For instance, here is the spacing changed to 80.
We then declare the variable / function node so that when we call it later it will know to select
the appropriate object (a node) with the appropriate .id.
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
The next block of code assigns the variable / function nodeEnter to the action of appending a
node to a particular position.
Tree Diagrams
258
Then we get to the piece of code that appends the circle that comprises the node (using
nodeEnter).
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
This is a neat piece of code that allows the text to be placed on the left side of the node if it has
children (d.children) or on the right if it has has no children or d._children. This is a slightly
redundant piece of code (the d._children piece) for this diagram, but it becomes more useful in
the interactive version towards the end of the chapter. It also aligns the text correctly and makes
sure it is visible.
Then we declare the link variable / function and tell it to make a link based on all the links that
have unique target ids.
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
This might not be obvious at first glance, but we only want to draw links between a node and its
parent. There should be one less link than the total number of nodes since the root node (Top
Level) has no parent. Therefore only those links with unique target ids in the data need to have
links produced. If we were to replace the .target in the above code with .source we would
have only two unique .source ids. It would therefore look like this;
259
Tree Diagrams
Our final block of JavaScript adds in our link as a diagonal path (as declared early in the JavaScript
portion of the code).
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
There are only a couple of lines of HTML to close off the file and we are left with our tree
diagram!
Dont forget, the full code for this example can be found on github, in the appendices of
this book or in the code samples bundled with this book (simple-tree-diagram.html). A working
example can be found on bl.ocks.org.
https://gist.github.com/d3noob/8323795
http://bl.ocks.org/d3noob/8323795
260
Tree Diagrams
Now, thats nice, but are we going to be satisfied with that??? (The answer is No by the way.)
This example is fairly simple, but it is an example of applying different styles to the nodes to
convey additional information. I should be clear at this stage that I am not advocating turning
your tree diagram into something that looks like it came out of a circus, because that would be
a crime against style, so dont repeat my upcoming example, but let some of the features be a
trigger for developing your own subtle, yet compelling visualizations.
Brace yourself. Heres a picture of the tree diagram that were going to generate. Those with
weaker constitutions should look away and flip forward a few pages;
The changes that have been made are as a result of additional data fields that have been added to
the JSON array and these fields have been applied to various style options throughout the code.
Tree Diagrams
261
The types of style changes we have made are - Variation of the diameter of nodes - Changing
the fill and stroke colour of nodes - Changing the colour of links depending on the associated
target node they connect to.
Well start by looking at the new JSON data set;
{
"name": "Top Level",
"parent": "null",
"value": 10,
"type": "black",
"level": "red",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"value": 15,
"type": "grey",
"level": "red",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A",
"value": 5,
"type": "steelblue",
"level": "orange"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"value": 8,
"type": "steelblue",
"level": "red"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"value": 10,
"type": "grey",
"level": "green"
}
]
}
Each node now has a value which might represent a degree of importance (we will use this to
Tree Diagrams
262
affect the radius of the nodes), a type which might indicate a difference in the type of node (they
might be in active, inactive or undetermined states) and a level which might indicate an alert
level for determining problems (red = bad, orange = caution and green = normal).
Irrespective of the contrived nature of our styling options, they are applied to our tree in fairly
similar ways with some subtle differences.
Remember, the full code for this example can be found on github or in the code samples
bundled with this book (simple-tree-features.html). A working example can be found on
bl.ocks.org.
The first change is to the node radius, stroke colour and fill colour.
We simply change the portion of the code that appends the circle from this
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
to this
nodeEnter.append("circle")
.attr("r", function(d) { return d.value; })
.style("stroke", function(d) { return d.type; })
.style("fill", function(d) { return d.level; });
The changes return the radius attribute as a function using value, the stroke colour is returned
using type and the fill colour is returned with level. This is nice and simple, but we do need
to make a slight adjustment to the code that sets the distance that the text is from the nodes so
that when the radius expands or contracts, the text distance from the edge of the node adjusts
as well.
To do this we take the clever piece of code that adjusts the distance that the text is in the x
dimension from the node that looks like this
.attr("x", function(d) {
return d.children || d._children ? -13 : 13; })
The last thing we wanted to do is to change the colour of the link based on the colour of the
target node. We accomplish this by taking the code that inserts the links
https://gist.github.com/d3noob/8324872
http://bl.ocks.org/d3noob/8324872
Tree Diagrams
263
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
and adding in a line that styles the link colour (the stroke) based on the level colour of the
target end of the link d.target.level).
link.enter().insert("path", "g")
.attr("class", "link")
.style("stroke", function(d) { return d.target.level; })
.attr("d", diagonal);
Use the concepts here wisely. I dont want to see any heinously styled tree diagrams floating
around the internet with Thanks to the help from D3 Tips and Tricks next to them. Be subtle,
be thoughtful :-).
Tree Diagrams
264
and swapping the d.x and d.y designators so that it looks like this
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
Because the vertical version of the tree diagram can be a lot more compact, we can adjust our
difference between the depths to a more rational value. In our example we can change the
separation from 180 to 100 pixels in the following line of code
nodes.forEach(function(d) { d.y = d.depth * 100; });
The second is to do the same adjustment for the links. We take the block of code that generates
the curvy diagonal paths
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
and swap the d.x and d.y designators so that it looks like this
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
At this point we have our tree diagram ready to go except for one small detail
265
Tree Diagrams
The text is still aligned to the left and right of the nodes. On this example, it looks pretty good,
but if we were to introduce a few more nodes, it would start to get pretty cramped, so we can
place the text above and below the nodes dependent on whether the node is a parent (above) or
a child on the bottom level (below).
To do this we take the original text appending code
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
and change the x attribute to a y attribute, anchor the text in the middle (which is actually
a simplification of the code) and extend the distance between the node and the anchor point
slightly to 18 (and -18) pixels.
nodeEnter.append("text")
.attr("y", function(d) {
return d.children || d._children ? -18 : 18; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
266
Tree Diagrams
The full code for this example can be found on github or in the code samples bundled with
this book (simple-tree-vertical.html). A working online example can be found on bl.ocks.org.
https://gist.github.com/d3noob/8326869
http://bl.ocks.org/d3noob/8326869
267
Tree Diagrams
"name"
"name"
"name"
"name"
"name"
:
:
:
:
:
It is actually fairly simple and consists of only the name of the node and the name of its parent
node. Its easy to see how this data could be developed into a hierarchical form, but it would take
a little time and for a larger data set, that would be tiresome.
Luckily computers are built for shuffling data about and with kudos to nrabinowitz for
answering a question (and Prateek Tandon for asking) on Stack Overflow (and Jesus Ruiz
with AmeliaBR for setting me on the right path), here is how we can take our flat data and
convert it for use in our tree diagram.
We will be using the simple example that we started with at the start of the chapter and the first
change we need to make is to replace our original data
var treeData = [
{
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
http://stackoverflow.com/questions/17847131/generate-multilevel-flare-json-data-format-from-flat-json
http://stackoverflow.com/questions/20940854/how-to-load-data-from-an-internal-json-array-rather-than-from-an-external-resour
268
Tree Diagrams
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
];
:
:
:
:
:
Its worth noting here that we have also changed the name of the array (to data) since we
are going to convert, then declare our newly massaged data with our original variable name
treeData so that the remainder of our code thinks there have been no changes.
Then we create a name-based map for the nodes. In his answer on Stack Overflow, nrabinowitz
uses the .reduce method, which starts with an empty object and iterates over the data array,
adding an entry for each node.
var dataMap = data.reduce(function(map, node) {
map[node.name] = node;
return map;
}, {});
Dont feel upset if you dont understand exactly how it works. I struggle to understand internal
combustion engines, but Im ok at driving a car :-). Think of this in the same way.
Then we iteratively add each child to its parents, or to the root array if no parent is found;
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
269
Tree Diagrams
The code is essentially working through each node in the array and if it has a child it adds it to
the children sub-array and if necessary creates the array. Likewise, if the node has no parent, it
simply add it as a root node.
Thats it!
The brevity of the code to do this should not detract from its elegance. It really is very clever.
The end result doesnt look any different from our original diagram
https://gist.github.com/d3noob/8329404
http://bl.ocks.org/d3noob/8329404
Tree Diagrams
270
(dont include the treeData = part, or the semicolon at the end (you can delete those))
Then all we need to do is change the portion of the code that declared the root variable and
updates the diagram;
Tree Diagrams
271
root = treeData[0];
update(root);
into a small section that uses the d3.json accessor to load the file treeData.json (Remember
to correctly address the file. This one assumes that the treeData.json file is in the same directory
as the html file we are opening).
d3.json("treeData.json", function(error, treeData) {
root = treeData[0];
update(root);
});
It then declares the variable root in the same way and calls the update function to draw the tree
diagram. Viola!
The full code for this example can be found on github or in the code samples bundled with
this book (simple-tree-from-external.html and treeData.json). A working example can be found
on bl.ocks.org.
https://gist.github.com/d3noob/8329447
http://bl.ocks.org/d3noob/8329447
272
Tree Diagrams
Tree diagram)
Then when clicking on the Level 2: A node, the tree partially collapses to
273
Tree Diagrams
We could also click on the root node (Top Level) to fully collapse the tree
The code then adds sections to allow the diagram to follow the d3.js model of enter - update exit for the nodes with a suitable transition in between.
Nodes are coloured (steelblue) if they have been collapsed and at the end of the script we
have a function that makes use of the d._children reference we have been using in most of our
examples.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
This allows the action of clicking on the nodes to update the data associated with the node and
as a consequence change its properties in the script based on if statements (Such as "fill",
function(d) { return d._children ? "lightsteelblue" : "#fff"; } which will fill the
node with lightsteelblue if d._children exists, otherwise make it white.)
The examples we have looked at in the previous sections in this chapter are all applicable to this
interactive version, so this should provide you with the capability to generate some interesting
visualizations.
Enjoy.
Simultaneous forces of repulsion and multiple gravitational focus points create a natural
clustering of data points (Source: Mike Bostock http://bl.ocks.org/mbostock/1249681). The
graph is animated, so the artefacts such as overlapping circles and the purple circle that is located
beside the red area are transitory.
Force Directed Graph with Pan / Zoom
http://vimeo.com/29458354
http://mbostock.github.com/d3/talk/20110921/#0
http://bl.ocks.org/mbostock/1249681
275
Multiple linked nodes show connections between related entities where those entities are
labelled and encoded with relevant information. Created by David Graus and presented here:
http://graus.nu/blog/force-directed-graphs-playing-around-with-d3-js/.
Collapsible Force Layout
This force directed graph can have individual nodes expanded or collapsed by clicking on them
to reveal or hide greater detail (Source: Mike Bostock http://bl.ocks.org/mbostock/1062288).
http://graus.nu/blog/force-directed-graphs-playing-around-with-d3-js/
http://bl.ocks.org/mbostock/1062288
276
This example showing mobile patent lawsuits between companies presents the direction associated with the links and encodes the links to show different types (Source: Mike Bostock
http://bl.ocks.org/mbostock/1153292).
Collision Detection
Collision Detection
In this example the mouse exerts a repulsive force on the objects as it moves on the screen (Source:
Mike Bostock http://bl.ocks.org/mbostock/3231298).
http://bl.ocks.org/mbostock/1153292
http://bl.ocks.org/mbostock/3231298
277
Molecule Diagram
Molecule Diagram
Just for fun, here is a diagram that Mike Bostock made to demonstrate drawing two parallel lines
between nodes. Hes the first to admit that increasing the number of lines becomes awkward,
but it serves as another example of the flexibility of force diagrams in D3 (Source: Mike Bostock
http://bl.ocks.org/mbostock/3037015).
The main forces in play in these diagrams are charge, gravity and friction. More detailed
information on these forces and the other parameters associated with the force layout code can
be found in the D3 Wiki.
Charge
Charge is a force that a node can exhibit where it can either attract (positive values) or repel
(negative values). Varying this value in conjunction with other forces (such as gravity) or a link
(on a node by node basis) is generally necessary to maintain stability.
Gravity
The gravity force isnt actually a true representation of gravitational attraction (this can be more
closely approximated using positive values of charge). Instead it approximates the action of a
spring connected to a node. This has a more pleasant visual effect when the affected node is
closer to its great attractor and avoids what would otherwise be a small black hole type effect.
Friction
The frictional force is one designed to act on the movement of a node to reduce its speed over
time. It isnt implemented as true friction (in the physical sense) and should be thought of as a
velocity decay in the truer sense.
Mike makes the point in the 2011 talk at Trulia that when using gravity in a force layout diagram,
it is useful to include a degree of charge repulsion to provide stability. This can be demonstrated
by experimenting with varying values of the charges in a diagram and observing the effects.
http://bl.ocks.org/mbostock/3037015
https://github.com/mbostock/d3/wiki/Force-Layout
278
for the directionality and link encoding and the Force-Directed Graph with Mouseover
graph
279
In this example the nodes can be clicked on once to enlarge the associated circle and text and then
double clicked on to return them to normal. The links vary in opacity depending on an associated
value loaded with the data. The example code for this graph can be found on bl.ocks.org.
Eveie,Harry,2.0
Henry,Mikey,0.4
Elric,Mikey,0.6
James,Sarah,1.5
Alice,Sarah,0.6
James,Maddy,0.5
Peter,Johan,0.7
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
</style>
<body>
<script>
280
281
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var dx = d.target.x d.source.x,
dy = d.target.y d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
});
282
283
</script>
</body>
</html>
In a similar process to the one we went through when highlighting the function of the Sankey
diagram, where there are areas that we have covered before, I will gloss over some details on the
understanding that you will have already seen them explained in an earlier section (most likely
the basic line graph example).
The first block we come across is the initial html section;
<!DOCTYPE html>
<meta charset="utf-8">
<script type="text/javascript" src="d3/d3.v3.js"></script>
<style>
The only thing slightly different with this example is that we load the d3.v3.js script earlier. This
has no effect on running the code.
The next section loads the Cascading Style Sheets;
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
We set styles for three elements and all the settings laid out are familiar to us from previous
work.
Then we move into the JavaScript. Our first line loads our csv data file (force.csv) from our
data directory.
284
Then we declare an empty object (I still tend to think of these as arrays even though theyre
strictly not).
var nodes = {};
This will contain our data for our nodes. We dont have any separate node information in our
data file, its just link information, so we will be populating this in the next section
links.forEach(function(link) {
link.source = nodes[link.source]
(nodes[link.source] = {name:
link.target = nodes[link.target]
(nodes[link.target] = {name:
link.value = +link.value;
});
||
link.source});
||
link.target});
This block of code looks through all of our data from our csv file and for each link adds it as
a node if it hasnt seen it before. Its quite clever how it works as it employs a neat JavaScript
shorthand method using the double pipe (||) identifier.
So the line (expanded)
link.source=nodes[link.source] || (nodes[link.source]={name: link.source});
can be thought of as saying If link.source does not equal any of the nodes values then create
a new element in the nodes object with the name of the link.source value being considered.. It
could conceivably be written as follows (this is untested);
if (link.source != nodes[link.source]) {
nodes[link.source] = {name: link.source}
};
Then the block of code goes on to test the link.target value in the same way. Then the value
variable is converted to a number from a string if necessary (link.value = +link.value;).
The next block sets the size of our svg area that well be using;
var width = 960,
height = 500;
285
Full details for this function are found on the D3 Wiki, but the following is a rough description
of the individual settings.
var force = d3.layout.force() makes sure were using the force function.
.nodes(d3.values(nodes)) sets our layout to the array of nodes as returned by the function
d3.values (https://github.com/mbostock/d3/wiki/Arrays#wiki-d3_values). Put simply, it sets
the nodes to the nodes we have previously set in our object.
.links(links) does for links what .nodes did for nodes.
.size([width, height]) sets the available layout size to our predefined values. If we were using
gravity as a force in the graph this would also set the gravitational centre. It also sets the initial
moves towards a steady state, the distance between each pair of linked nodes is computed and
compared to the target distance; the links are then moved towards or away from each other, so
as to converge on the set distance.
Setting this value (and other force values) can be something of a balancing act. For instance, here
is the result of setting the .linkDistance to 160.
https://github.com/mbostock/d3/wiki/Force-Layout
https://github.com/mbostock/d3/wiki/Arrays#wiki-d3_values
286
Here the charged nodes are trying to arrange themselves at an appropriate distance, but the
length of the links means that their arrangement is not very pretty. Likewise if we change the
value to 30 we get the following;
Here the link distance allows for a symmetrical layout, but the distance is too short to be practical.
.charge(-300) sets the force between nodes. Negative values of charge results in node repulsion,
while a positive value results in node attraction. In our example, if we vary the value to 150 we
get this result;
287
Its not exactly easy to spot, but the graph feels a little lazy. The nodes dont find their
equilibrium easily or at all. Setting the value higher than 300 (for our example) keeps all the
nodes nice and spread out, but where there are other separate discrete linked nodes (as there are
in our example) they tend to get forced away from the centre of the defined area.
.on("tick", tick) runs the animation of the force layout one step. Its this progression of
steps that gives the force layout diagram its fluid movement.
.start(); Starts the simulation; this method must be called when the layout is first created.
The next block of our code is the standard section that sets up our svg container.
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
The next block of our code is used to create our arrowhead marker. I will be the first to admit that
it has entered a realm of svg expertise that I do not have and the amount of extra memory power I
would need to accumulate to understand it sufficiently to explain wont be occurring in the near
future. Please accept my apologies and as a small token of my regret, accept the following links
as an invitation to learn more: http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
and http://www.w3schools.com/svg/svg_reference.asp?. What is useful to note here is that
we define the label for our marker as end. We will use this in the next section to reference the
marker as an object. This particular section of the code caused me some small amount of angst.
The problem being when I attempted to adjust the width of the link lines in conjunction with the
value set in the data for the link, it would also adjust the stroke-width of the arrowhead marker.
Then when I attempted to adjust for the positioning of the arrow on the path, I could never get
the maths right. Eventually I decided to stop struggling against it and encode the value of the
line in a couple of different ways. One as opacity using discrete boundaries and the other using
variable line width, but with the arrowheads a common size. We will cover both those solutions
in the coming sections.
http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
http://www.w3schools.com/svg/svg_reference.asp?
288
svg.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
The .data(["end"]) line sets our tag for a future part of the script to find this block and draw
the marker.
.attr("refX", 15) sets the offset of the arrow from the centre of the circle. While it is designated
as the X offset, because the object is rotating, it doesnt correspond to the x (left and right) axis
of the screen. The same is true of the .attr("refY", -1.5) line.
The .attr("markerWidth", 6) and .attr("markerHeight", 6) lines set the bounding box for
the marker. So varying these will vary the space available for the marker.
The next block of code adds in our links as paths and uses the #end marker to draw the arrowhead
on the end of it.
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
This uses the nodes data and adds the .call(force.drag); function which allows the node to
be dragged by the mouse.
The next block adds the nodes as an svg circle.
289
node.append("circle")
.attr("r", 5);
And then we add the name of the node with a suitable offset.
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
The last block of JavaScript is the ticks function. This block is responsible for updating the graph
and most interestingly drawing the curvy lines between nodes.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x d.source.x,
dy = d.target.y d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
This is another example where there are some easily recognisable parts of the code that set the
x and y points for the ends of each link (d.source.x, d.source.y for the start of the curve and
d.target.x, d.target.y for the end of the curve) and a transformation for the node points, but
the cleverness is in the combination of the math for the radius of the curve (dr = Math.sqrt(dx
* dx + dy * dy);) and the formatting of the svg associated with it. This is sadly beyond the
scope of what I can comfortable explain, so we will have to be content with the magic happens
here.
The end result should be a tidy graph that demonstrates nodes and directional links between
them.
290
The code and data for this example can be found as Basic Directional Force Layout Diagram
on bl.ocks.org.
http://bl.ocks.org/d3noob/5141278
291
The two additional lines above tell the script that when it sees a click or a double-click on the
node (since its part of the node set-up) to run either the click or dblclick functions.
The following two function blocks should be placed after the tick function but before the closing
curly bracket and bracket as indicated;
function tick() {
path.attr("d", function(d) {
var dx = d.target.x d.source.x,
dy = d.target.y d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
// <= Put the functions in here!
});
292
function click() {
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 22)
.style("fill", "steelblue")
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16)
.style("fill", "lightsteelblue");
}
The first line declares the function name (click). Then we select the node that weve clicked on
and then the associated text before we begin the declaration for our transition with the following;
d3.select(this).select("text").transition()
Then we define the new properties that will be in place after the transition. We move the texts x
position (.attr("x", 22)), make the text fill steel blue (.style("fill", "steelblue")), set the
stroke around the edge of the text light steel blue (.style("stroke", "lightsteelblue")), set
that stroke to half a pixel wide (.style("stroke-width", ".5px")) and increase the font size to
20 pixels (.style("font", "20px sans-serif");).
Then we do much the same for the circle component of the node. Select it, declare the transition,
increase the radius and change the fill colour.
The dblclick function does exactly the same as the click function, but reverses the action to
return the text and circle to the original settings.
function dblclick() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 6)
.style("fill", "#ccc");
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 12)
.style("stroke", "none")
.style("fill", "black")
.style("stroke", "none")
.style("font", "10px sans-serif");
}
The end result is a force layout diagram where you can click on nodes to increase their size (circle
and text) and then double click to reset them if desired.
293
The code and data for this example can be found as Directional Force Layout Diagram with Node
Highlighting on bl.ocks.org.
294
The changes to the code to create this effect are focussed on creating an appropriate range for
the values associated with the links and then applying the opacity according to that range in
discrete steps.
The first change to the node highlighting code that we make is to the style section. The following
elements are added;
path.link.twofive {
opacity: 0.25;
}
path.link.fivezero {
opacity: 0.50;
}
path.link.sevenfive {
opacity: 0.75;
}
path.link.onezerozero {
opacity: 1.0;
}
var
295
v = d3.scale.linear().range([0, 100]);
Here we set the scale and the range for the variable v (var v = d3.scale.linear().range([0,
100]);). We then set the domain for v to go from 0 to the maximum value that we have in our
link data.
The final block above uses a cascading set of if statements to assign a label to the type parameter
of each link. This label is the linkage back to the styles we defined previously.
The final change is to take the line where we assigned a class of link to each link previously
.attr("class", "link")
Obviously if we wanted a greater number of opacity levels we would add in further style blocks
(with the appropriate values) and modify our cascading if statements. Im not convinced that
this solution is very elegant for what Im trying to do (it was a much better fit for the application
that Mike Bostock applied it to originally where he designated different types of law suits) but
Ill take the result as a suitable way of demonstrating variation of value.
The code and data for this example can be found as Directional Force Layout Diagram with
varying link opacity on bl.ocks.org.
The full code for the Directional Force Layout Diagram with varying link opacity is also in the
Appendix: Force Layout Diagram at the rear of the book.
http://bl.ocks.org/d3noob/5155181
296
Here each of the nodes has had a separate colour applied to it from one of the 20 colour palette
categorical colour ranges. An excellent overview of these ranges is on the d3 wiki.
A live example of the code can be found on bl.ocks.org and GitHub.
The changes required from the previous example with the altered opacity are pretty simple.
Firstly we declare the colour range were going to use in the variable section.
color = d3.scale.category20c();
297
The code applies the fill based on a function that returns a different colour based on each unique
node name. So just to be clear here. Were not setting a specific colour to a node. The colours are
assigned as a function of where each name sits in the array of nodes (practically random, but in
an ordered way :-)).
Then remove the style declarations in the function click() and function dblclick() where
the fill colour is declared for the circles. This prevents the colours from turning grey or steelblue
when they are clicked / double clicked. This means that we can click on a few of our new coloured
nodes and their unique colours are retained thusly
Bullet Charts
Introduction to bullet chart structure
One of the first D3.js examples I ever came across (back when Protovis was the thing to use) was
one with bullet charts (or bullet graphs).
It struck me straight away as an elegant way to represent data by providing direct information
and context.
Bullet Chart
The Bullet Graph Design Specification was laid down by Stephen Frew as part of his work
with Perceptual Edge.
Using his specification we can break down the components of the chart as follows.
Bullet Charts
299
Comparative marker: A reference symbol designating a measurement such as the previous days
high value (or similar).
Qualitative ranges: These represent ranges such as low, medium and high or bad, satisfactory
and good. Ideally there would be no fewer than two and no more than 5 of these (for the purposes
of readability).
Understanding the specification for the chart is useful, because its also reflected in the way that
the data for the chart is structured.
For instance, If we take the current example, the data can be presented (in JSON) as follows;
[
{
"title":"CPU 1 Load",
"subtitle":"GHz",
"ranges":[1500,2250,3000],
"measures":[2200],
"markers":[2500]
}
]
Here we an see all the components for the chart laid out and its these values that we will load
into our D3 script to display.
Bullet Charts
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
padding-top: 40px;
position: relative;
width: 800px;
}
button {
position: absolute;
right: 40px;
top: 10px;
}
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
</style>
<button>Update</button>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/bullet.js"></script>
<script>
300
Bullet Charts
301
</script>
</body>
This code is a derivative of one of Mike Bostocks blocks here. You can download it (and a
data set with two bullet chart groups in it) from https://gist.github.com/d3noob/5886992. You
can view an online version here.
http://bl.ocks.org/mbostock/4061961
http://bl.ocks.org/d3noob/5886992
302
Bullet Charts
It will become clearer in the process of going through the code below, but as a teaser, it
is worth noting that while the code that we will modify is as presented above, we are
employing a separate script bullet.js to enable the charts.
The first block of our code is the start of the file and sets up our HTML.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
We declare the (general) styling for the chart page in the first instance and then the button. Then
we move on to the more interesting styling for the bullet charts.
The first line .bullet { font: 10px sans-serif; } sets the font size.
The second line sets the colour and width of the symbol marker. So if we were to change it to
.bullet .marker { stroke: red; stroke-width: 10px; }
303
Bullet Charts
the result is
Symbol Marker
The next three lines set the colours for the fill of the qualitative ranges.
.bullet .range.s0 { fill: #eee; }
.bullet .range.s1 { fill: #ddd; }
.bullet .range.s2 { fill: #ccc; }
You can have more or fewer ranges set here, but to use them you also need the appropriate values
in your data file. We will explore how to change this later.
The next line designates the colour for the value being measured.
.bullet .measure.s0 { fill: steelblue; }
Like the qualitative ranges, we can have more of them, but in my personal opinion, it starts to
get a bit confusing.
The final two lines lay out the styling for the label.
The next block of code loads the JavaScript files.
</style>
<button>Update</button>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/bullet.js"></script>
<script>
In this case its d3 and bullet.js. We need to load bullet.js as a separate file since it exists
outside the code base of the d3.js kernel.
Then we get into the JavaScript. The first thing we do is define the size of the area that well be
working in.
var margin = {top: 5, right: 40, bottom: 20, left: 120},
width = 800 - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
Then we define the chart size using the variables that we have just set up.
Bullet Charts
304
The other important thing that occurs while setting up the chart is that we use the d3.bullet
function call to do it. The d3.bullet function is the part that resides in the bullet.js file that
we loaded earlier. The internal workings of bullet.js are a window into just how developers
are able to craft extra code to allow additional functionality for d3.js.
Then we load our JSON data with our values that we want to display.
d3.json("data/cpu1.json", function(error, data) {
The next block of code is the most important IMHO, since this is where the chart is drawn.
var svg = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
However, to look at it you can be forgiven for wondering if its doing anything at all.
We use our .select and .selectAll statements to designate where the chart will go (d3.select("body").select
and then load the data as data (.data(data)).
We add in a svg element (.enter().append("svg")) and assign the styling from our css section
(.attr("class", "bullet")).
Then we set the size of the svg container for an individual bullet chart using .attr("width",
width + margin.left + margin.right) and .attr("height", height + margin.top +
margin.bottom).
We then group all the elements that make up each individual bullet chart with .append("g") before placing the group in the right place with .attr("transform", "translate(" + margin.left
+ "," + margin.top + ")").
Then we wave the magic wand and call the chart function with .call(chart); which will take
all the information from our data file ( like the ranges, measures and markers values) and use
the bullet.js script to create a chart.
The reason I made the comment about the process looking like magic is that the vast majority of
the heavy lifting is done by the bullet.js file. Because its abstracted away from the immediate
code that were writing, it looks simplistic, but like all good things, there needs to be a lot of
complexity to make a process look simple.
We then add the titles.
Bullet Charts
305
We do this in stages. First we create a variable title which will append objects to the grouped element created above (var title = svg.append("g")). We apply a style (.style("text-anchor",
"end")) and transform to the objects (.attr("transform", "translate(-6," + height / 2 +
")");).
Then we append the title and subtitle data (from our JSON file) to our chart with a modicum
of styling and placement.
Then we add a button and functions which do the job of applying random data to our variables
every time its pressed.
d3.selectAll("button").on("click", function() {
svg.datum(randomize).call(chart.duration(1000));
});
});
function randomize(d) {
if (!d.randomizer) d.randomizer = randomizer(d);
d.markers = d.markers.map(d.randomizer);
d.measures = d.measures.map(d.randomizer);
return d;
}
function randomizer(d) {
var k = d3.max(d.ranges) * .2;
return function(d) {
return Math.max(0, d + k * (Math.random() - .5));
};
}
Im not going to delve into the working of the randomize function, because it exists simply to
demonstrate the dynamic nature of the chart and not really how the chart is drawn.
However, I will be going through a process later to ensure that we can update the data and the
chart automatically which will hopefully be more orientated to practical applications.
Bullet Charts
306
Thats it! Now well go through how you can use the data to change aspects of the chart and
what parts of the code need to be adjusted to work with those changes.
This is perfectly valid data, but well find it slightly easier to understand if we show it like this
[
{
"title":"CPU Load",
"subtitle":"GHz",
"ranges":[1500,2250,3000],
"measures":[2200],
"markers":[2500]
},
{
"title":"Memory Used",
"subtitle":"MBytes",
"ranges":[256,512,1024],
"measures":[768],
"markers":[900]
}
]
The data is exactly the same (in terms of content) but I find it a lot easier to comprehend whats
going on with the second example.
307
Bullet Charts
I have a section in the book called Understanding JavaScript Object Notation (JSON)
in the Assorted Tips and Tricks chapter. I found life a lot easier once I started to
understand how data was structured in JSON, and if you take a bit of time to understand
it, I think youll find life easier too.
You dont need to make any changes to your code in order to add more individual charts. You
just need to add more data groups to your JSON file. The following example uses exactly the
same code, but with several extra groups of data.
Bullet Charts
308
[
{
"title":"CPU 1 Load",
"subtitle":"GHz",
"ranges":[1500,2250,3000],
"measures":[2200],
"markers":[2500]
}
]
The same was true for the css in the JavaScript code. Three ranges and one measure
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
By matching the css for the .bullet style with the data you can add more or fewer of both. For
example heres example data, css and a chart with five ranges and two measures.
[
{
"title":"CPU 1 Load",
"subtitle":"GHz",
"ranges":[500,1000,1500,2250,3000],
"measures":[1250, 2200],
"markers":[2650]
}
]
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
309
Bullet Charts
First of all. Yes, I know the colours are gaudy. Hopefully they stand out. Dont abuse your own
graphs in this hideous way.
More importantly though, you can now get a better idea of how to align the range and measure
values in the JSON file with the .range and .measure styles in the css.
The diagram shows that the .range and .measure bars are numbered from the right. (for example
the navy colour showing the range up to 3000 GHz is designated .range.s0. At first this
convention of numbering from the right confused me. I imagined that the smallest range should
be .range.s0 and this should be on the left. Then I realised that the numbering related to the
layer of the range. So this would make .range.s0 go from 0 to 3000. Then the second layer
would be .range.s1 which would go on top of .range.s0 from 0 to 2250, thereby covering most
of .range.s0 except for the part that exceeded .range.s1. Which is exactly what we see with
successively higher layers having higher numbers. The same is true for the .measure numbers
and layers.
Bullet Charts
310
button {
position: absolute;
right: 40px;
top: 10px;
}
Then remove this line that added the button in the html section;
<button>Update</button>
All we need to do now is change the section that called the original json file from;
d3.json("data/cpu1.json", function(error, data) {
to
d3.json("data/bulletdata2.json", function(error, data) {
So that were dealing with a different json file (theres no need to go messing around with our
original data).
Change the section that used to call the function to randomise the data with the button click
from
d3.selectAll("button").on("click", function() {
svg.datum(randomize).call(chart.duration(1000));
});
to
setInterval(function() {
updateData();
}, 1000);
This new piece of code simply sets up a repeating function that calls another function (updateData)
every 1000ms.
The final change is to replace the original functions that randomised the data
Bullet Charts
311
function randomize(d) {
if (!d.randomizer) d.randomizer = randomizer(d);
d.markers = d.markers.map(d.randomizer);
d.measures = d.measures.map(d.randomizer);
return d;
}
function randomizer(d) {
var k = d3.max(d.ranges) * .2;
return function(d) {
return Math.max(0, d + k * (Math.random() - .5));
};
}
This new function (updateData) reads in our json file again, selects all the svg elements then
updates all the .ranges, .measures and .markers data with whatever was in the file. Then it
calls the chart function that updates the bullet charts.
All the code components for this script can be downloaded from GitHub. A live version can
be viewed on bl.ocks.org (although it wont update since the data file cant be updated online).
https://gist.github.com/d3noob/5893649
http://bl.ocks.org/d3noob/5893649
Examples
I am firmly of the belief that mapping in particular has an enormous potential for adding value to
data sets. The following collection of examples gives a brief taste of what has been accomplished
by combining geographic information and D3 thus far. (The screen shots following have been
sourced from the biovisualize gallery and as such provide attribution to the best of my ability.
If I have incorrectly attributed the source or author please let me know and I will correct it
promptly.)
Above is an interactive visualization showing the position of the main map on a faux D3 3d
globe with a Mapbox / Open Street Map main window. Source dev.geosprocket.com Source
Bill Morris.
http://www.jasondavies.com/
http://www.jasondavies.com/maps/
http://biovisualize.github.com/d3visualization/#visualizationType=map
http://dev.geosprocket.com/d3/finder/
313
This is a breakdown of population in Kentucky Counties from the 2010 census. Source:
ccarpenterg.github.com by Cristian Carpenter.
This map visualizes air pollution in Beijing. Source: scottcheng.github.com by Scott Cheng.
http://ccarpenterg.github.com/blog/us-census-visualization-with-d3js/
http://scottcheng.github.com/bj-air-vis/
314
This is a section of the globe that is presented on the Shuttle Radar Topography Mission tile
downloading web site. This excellent site uses the interactive globe to make the selection of
SRTM tiles easy. Source dwtkns.com by Derek Watkins.
This is a static screen-shot of an animated tour of the Worlds countries. Source bl.ocks.org by
Mike Bostock.
http://dwtkns.com/srtm/
http://bl.ocks.org/mbostock/4183330
315
This is one of the great infographics published by the New York Times. Source: www.nytimes.com
by Mike Bostock, Shan Carter and Kevin Quealy.
This is an animated graphic showing a series of concentric circles emanating from glowing red
dot which was styled after a news article in The Onion. Source: bl.ocks.org by Mike Bostock.
http://www.nytimes.com
http://www.nytimes.com/interactive/2013/01/02/us/chicago-killings.html?_r=0
http://www.theonion.com/video/breaking-news-series-of-concentric-circles-emanati,14204/
http://bl.ocks.org/mbostock/4503672
316
Here we see earthquakes represented on a selectable timeline where D3 generates a svg overlay
and the map layer is created using Leaflet. Source: bl.ocks.org by tnightingale.
Carrying on with the earthquake theme, this is a map of all earthquakes in the past 24 hours over
magnitude 2.5. Source: bl.ocks.org by benelsen.
http://bl.ocks.org/tnightingale/4718717
http://bl.ocks.org/benelsen/4969007
317
Satellite projection
http://dev.geosprocket.com/d3/sat/
https://github.com/mbostock/topojson/wiki
318
The World
The data file for the World map is one produced by Mike Bostocks as part of his TopoJSON
work.
Well move through the explanation of the code in a similar process to the one we went through
when highlighting the function of the Sankey diagram. Where there are areas that we have
covered before, I will gloss over some details on the understanding that you will have already
seen them explained in an earlier section (most likely the basic line graph example).
Here is the full code;
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
319
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/topojson.v0.min.js"></script>
<script>
</script>
</body>
</html>
One of the first things that struck me when I first saw the code to draw a map was how small
it was (the amount of code, not the World). Its a measure of the degree of abstraction that D3
is able to provide to the process of getting data from a raw format to the screen that such a
complicated task can be condensed to such an apparently small amount of code. Of course that
doesnt tell the whole story. Like a duck on a lake, above the water all is serene and calm while
below the water the feet are paddling like fury. In this case, our code looks serene because D3 is
doing all the hard work :-).
The first block of our code is the start of the file and sets up our HTML.
320
<!DOCTYPE html>
<meta charset="utf-8">
<style>
We only state the properties of the path components which will make up our countries. Obviously
we will fill them with grey and have a thin (0.25px) line around each one.
The next block of code loads the JavaScript files.
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/topojson.v0.min.js"></script>
<script>
In this case its d3 and topojson. We load topojson.v0.min.js as a separate file because its still
fairly new. In other words it hasnt been incorporated into the main d3.js code base (thats an
assumption on my part since it might exist in isolation or perhaps end up as a plug-in). Whatever
the case, for the time being, it exists as a separate file.
Then we get into the JavaScript. The first thing we do is define the size of our map.
var width = 960,
height = 500;
Then we get into one of the simple, but cool parts of making any map. Setting up the view.
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(900)
.rotate([-180,0]);
The projection is the way that the geographic coordinate system is adjusted for display on our
flat screen. The screen is after all a two dimensional space and we are trying to present a three
dimensional object. This is a big deal to cartographers in the sense that selecting a geographic
projection for a map is an exercise in compromise. You can make it look pretty, but in doing
so you can grievously distort the land size / shape. On the other hand you might make it more
321
accurate, in size / shape but people will have trouble recognising it because theyre so used to
the standard Mercator projection. For example, the awesome Waterman Butterfly.
There are a lot of alternatives available. Please have a browse on the wiki where you will find
a huge range of options (66 at time of writing).
In our case weve gone with the conservative Mercator option.
Then we define three aspects of the projection. Center, scale and rotate.
http://bl.ocks.org/mbostock/4458497
https://github.com/mbostock/d3/wiki/Geo-Projections
322
center
If center is specified, this sets the projections center to the specified location as a two-element
array of longitude and latitude in degrees and returns the projection. If center is not specified
the default of (0,0) is used.
Our example is using [0, 5 ] which I have selected as being in the middle (I use 0) for longitude
(left to right) and 5 degrees North of the equator (for latitude, North is positive, South is negative).
This was purely to make it look aesthetically pleasing. Heres the result of setting the center to
[100,30].
The map has been centered on 100 degrees West and 30 degrees North. Of course, its also been
pushed to the left without the right hand side of the map scrolling around. Well get to that in a
moment.
323
scale
If scale is specified, this sets the projections scale factor to the specified value. If scale is not
specified, it returns the current scale factor which defaults to 150. Its important to note that
scale factors are not consistent across projections.
Our current map uses a scale of 900. Again, this has been set for aesthetics. Keeping our center
of [100,30], if we increase our scale to 2000 this is the result.
324
rotate
If rotation is specified, this sets the projections three-axis rotation to the specified angles for yaw,
pitch and roll (equivalently longitude, latitude and roll) in degrees and returns the projection. If
rotation is not specified, it sets the values to [0, 0, 0]. If the specified rotation has only two values,
rather than three, the roll is assumed to be 0.
In our map we have specified [-180,0] so we can assume a roll value of zero. Likewise we have
rotated our map by -180 degrees in longitude. This has been done specifically to place the map
with the center on the anti-meridian (The international date line in the middle of the Pacific
Ocean). If we return the value to [0,0](with our original values of scale and center this is the
result.
In this case the centre of the map lines up with the meridian.
The next block of code sets our svg window;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
The path generator (d3.geo.path()) is used to specify a projection type (.projection) which
was defined earlier as a Mercator projection via the variable projection. (Im not entirely sure,
325
but it is possible that I have just set some kind of record for use of the word projection in a
sentence.)
We then declare g as our appended svg.
var g = svg.append("g");
We load the TopoJSON file with the coordinates for our World map (world-110m2.json). Then
we declare that we are going to act on all the path elements in the graphic (g.selectAll("path")).
Then we pull the data that defines the countries from the TopoJSON file (.data(topojson.object(topology,
topology.objects.countries).geometries)). We add it to the data that were going to display
(.enter()) and then we append that data as path elements (.append("path")).
The last html block closes off our tags and we have a map!
326
The code and data for this example can be found as World Map Centered on the Pacific on
bl.ocks.org.
This block of code introduces the behaviors functions. Using the d3.behavior.zoom function
creates event listeners (which are like hidden functions standing by to look out for a specific
type of activity on the computer and in this case mouse actions) to handle zooming and panning
gestures on a container element (in this case our map). More information on the range of zoom
options is available on the D3 Wiki.
We begin by declaring the zoom function as d3.behavior.zoom.
Then we instruct the computer that when it sees a zoom event to carry out another function
(.on("zoom",function() {).
That function firstly gathers the (correctly formatted) translate and scale attributes in
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
and then applies them to all the path elements (which are the shapes of the countries) via
g.selectAll("path")
.attr("d", path.projection(projection));
327
svg.call(zoom)
The code and data for this example can be found as World Map with zoom and pan on
bl.ocks.org.
http://bl.ocks.org/d3noob/5189284
http://www.openstreetmap.org/
328
code,city,country,lat,lon
ZNZ,ZANZIBAR,TANZANIA,-6.13,39.31
TYO,TOKYO,JAPAN,35.68,139.76
AKL,AUCKLAND,NEW ZEALAND,-36.85,174.78
BKK,BANGKOK,THAILAND,13.75,100.48
DEL,DELHI,INDIA,29.01,77.38
SIN,SINGAPORE,SINGAPOR,1.36,103.75
BSB,BRASILIA,BRAZIL,-15.67,-47.43
RIO,RIO DE JANEIRO,BRAZIL,-22.90,-43.24
YTO,TORONTO,CANADA,43.64,-79.40
IPC,EASTER ISLAND,CHILE,-27.11,-109.36
SEA,SEATTLE,USA,47.61,-122.33
While were only going to use the latitude and longitude for our current work, the additional
details could just as easily be used for labeling or tooltips.
We need to place our code carefully in this case because while you might have some flexibility
in getting the right result with a locally hosted version of a map, there is a possibility that with
a version hosted in the outside World (gasp the internet) you could strike trouble.
The code to load the cities should be placed inside the function that is loading the World map as
indicated below;
d3.json("json/world-110m2.json", function(error, topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
// <== Put the new code block here
});
329
Well go through the code and then explain the quirky thing about it.
First of all we load the cities.csv file (d3.csv("data/cities.csv", function(error, data)
{). Then we select all the circle elements ( g.selectAll("circle")), assign our data (.data(data)),
enter our data ( .enter()) and then add in circles (.append("circle")).
Then we set the x and y position for the circles based on the longitude (([d.lon, d.lat])[0])
and latitude (([d.lon, d.lat])[1]) information in the csv file.
Finally we assign a radius of 5 pixels and fill the circles with red.
The quirky thing about the new code block is that we have to put it inside the code block that
loads the world data (d3.json("json/world-110m2.json", function(error, topology) {).
We could place the two blocks one after the others (load / draw the world data, then load / draw
the circles). And this will probably work if you run the file from your local computer. But when
you host the files on the internet, it takes too long to load the world data compared to the city
data and the end result is that the city data gets drawn before the world data and this is the
result.
To avoid the problem we place the loading of the city data into the code that loads the World
data. That way the city data doesnt get loaded until the World data is loaded and then the circles
get drawn on top of the world instead of under it :-).
330
The code and data for this example can be found as World map with zoom / pan and cities on
bl.ocks.org.
Additionally the full code can be found in the appendix section at the rear of the book.
http://bl.ocks.org/d3noob/5193723
331
leaflet.js Overview
Leaflet.js is an Open Source JavaScript library designed to make deploying maps on a web
page easy. It uses a paltry 34kB (at time of writing) JavaScript file that loads with your web page
and provides access to a range of functions that will allow you to present a map.
Its goals are to be simple to use while focussing on performance and usability, but its also built
to be extended using plugins that extend its functionality. It has an excellent API which is well
documented, so there are no mysteries to using it successfully in a range of situations.
Out of the box Leaflet provides the functionality to add markers, popups, overlay lines and
shapes, use multiple layers, zoom, pan and generally have a good time :-). But these are just the
the core features of Leaflet. One of the significant strengths of Leaflet is the ability to extend
the functionality of the script with plugins from third parties. At the time of writing there are
over 80 separate plugins that allow features such as overlaying a heatmap, animating markers,
loading csv files of data, drawing complex shapes, measuring distance, manipulating layers and
displaying coordinates.
Leaflet is simple, elegant and functional but powerful. Theres a good chance that even if you
dont present maps with Leaflet, youll be using ones that someone else made with it at some
stage on the Internet.
Why use leaflet.js when d3.js does maps too?
Good question. I can see youve been paying attention.
There is a significant difference between the underlying way that d3.js and leaflet.js presents
mapping data. D3.js predominantly focusses on vector based graphics when drawing maps and
leaflet.js leverages the huge range of bitmap based map tiles that are available for use around
the world. Both bitmap and vector based solutions have strengths and weaknesses depending on
the application. Combining both allows the use of the best of both worlds.
https://leanpub.com/leaflet-tips-and-tricks
http://leafletjs.com/
332
Leaflet map with d3.js objects that scale with the map
The first example well look at will project a leaflet.js map on the screen with a d3.js object (in
this case a simple rectangle) onto the map.
The rectangle will be bound to a set of geographic coordinates so that as the map is panned and
zoomed the rectangle will shrink and grow. For example the following diagram shows a rectangle
(made with d3.js ) superimposed over a leaflet.js map;
If we then zoom in
333
334
<script src="http://d3js.org/d3.v3.min.js"></script>
<script
src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js">
</script>
<script>
var map = L.map('map').setView([-41.2858, 174.7868], 13);
mapLink =
'<a href="http://openstreetmap.org">OpenStreetMap</a>';
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© ' + mapLink + ' Contributors',
maxZoom: 18,
}).addTo(map);
// Add an SVG element to Leaflets overlay pane
var svg = d3.select(map.getPanes().overlayPane).append("svg"),
g = svg.append("g").attr("class", "leaflet-zoom-hide");
d3.json("rectangle.json", function(geoShape) {
// create a d3.geo.path to convert GeoJSON to SVG
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform);
// create path elements for each of the features
d3_features = g.selectAll("path")
.data(geoShape.features)
.enter().append("path");
map.on("viewreset", reset);
reset();
// fit the SVG element to leaflet's map layer
function reset() {
bounds = path.bounds(geoShape);
var topLeft = bounds[0],
bottomRight = bounds[1];
svg .attr("width", bottomRight[0] - topLeft[0])
335
There is also an associated json data file (called rectangle.json) that has the following contents;
{
"type": "FeatureCollection",
"features": [ {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [ [
[ 174.78, -41.29 ],
[ 174.79, -41.29 ],
[ 174.79, -41.28 ],
[ 174.78, -41.28 ],
[ 174.78, -41.29 ]
] ]
}
}
]
}
336
The full code and a live example are available online at bl.ocks.org or GitHub. They are
also available as the files leaflet-d3-combined.html and rectangle.json as a separate download
with D3 Tips and Tricks. A a copy of all the files that appear in the book can be downloaded (in
a zip file) when you download the book from Leanpub
While I will explain the code below, please be aware that I will gloss over some of the simpler
sections that are covered in other sections of either books and will instead focus on the portions
that are important to understand the combination of d3 and leaflet.
Our code begins by setting up the html document in a fairly standard way.
<!DOCTYPE html>
<html>
<head>
<title>Leaflet and D3 Map</title>
<meta charset="utf-8" />
<link
rel="stylesheet"
href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"
/>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script
src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js">
</script>
Here were getting some css styling and loading our leaflet.js / d3.js libraries. The only
configuration item is where we set up the size of the map (in the <style> section and as part of
the map div).
Then we break into the JavaScript code. The first thing we do is to project our Leaflet map;
http://bl.ocks.org/d3noob/9211665
https://gist.github.com/d3noob/9211665
https://leanpub.com/leaflet-tips-and-tricks
337
This is exactly the same as we have done in any of the simple map explanations in Leaflet Tips
and Tricks and in this case we are using the OpenStreetMap tiles.
Then we start on the d3.js part of the code.
The first part of that involves making sure that Leaflet and D3 are synchronised in the view that
theyre projecting. This synchronisation needs to occur in zooming and panning so we add an
SVG element to Leaflets overlayPlane
var svg = d3.select(map.getPanes().overlayPane).append("svg"),
g = svg.append("g").attr("class", "leaflet-zoom-hide");
Then we add a g element that ensures that the SVG element and the Leaflet layer have the same
common point of reference. Otherwise when they zoomed and panned it could be offset. The
leaflet-zoom-hide affects the presentation of the map when zooming. Without it the underlying
map zooms to a new size, but the d3.js elements remain as they are until the zoom effect has taken
place and then they adjust. It still works fine, but it looks wrong.
Then we load our data file with the line
d3.json("rectangle.json", function(geoShape) {
This is pretty standard fare for d3.js but its worth being mindful that while the type of data file
is .json this is a GeoJSON file and they have particular features (literally) that allow them to do
their magic. There is a good explanation of how they are structured at geojson.org for those
who are unfamiliar with the differences.
Using our data we need to ensure that it is correctly transformed from our latitude/longitude
coordinates as supplied to coordinates on the screen. We do this by implementing d3s geographic
transformation features (d3.geo).
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform);
Here the path that we want to create in SVG is generated from the points that are supplied from
the data file which are converted by the function projectPoint This function (which is placed
at the end of the file) takes our latitude and longitudes and transforms them to screen (layer)
coordinates.
https://leanpub.com/leaflet-tips-and-tricks/
http://geojson.org/geojson-spec.html
https://github.com/mbostock/d3/wiki/API-Reference#wiki-d3geo-geography
http://leafletjs.com/reference.html#map-conversion-methods
338
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
With the transformations now all taken care of we can generate our path in the traditional d3.js
way and append it to our g group.
d3_features = g.selectAll("path")
.data(geoShape.features)
.enter().append("path");
The last main part of our JavaScript makes sure that when our view of what were looking at
changes (we zoom or pan) that our d3 elements change as well;
map.on("viewreset", reset);
reset();
Obviously when our view changes we call the function reset. Its the job of the reset function
to ensure that whatever the leaflet layer does, the SVG (d3.js) layer follows;
function reset() {
bounds = path.bounds(geoShape);
var topLeft = bounds[0],
bottomRight = bounds[1];
svg .attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
g .attr("transform", "translate(" + -topLeft[0] + ","
+ -topLeft[1] + ")");
// initialize the path data
d3_features.attr("d", path)
.style("fill-opacity", 0.7)
.attr('fill','blue');
}
339
It does this by establishing the topLeft and bottomRightcorners of the desired area and then
it applies the width, height, top and bottom attributes to the svg element and translates the g
element to the right spot. Last, but not least it redraws the path.
The end result being a fine combination of leaflet.js map and ds.js element;
340
d3.js circles fixed in geographic location on leaflet map but constant size
When we zoom out of the map, those circles remain over the geographic location, but the same
size on the screen.
Zoomed d3.js circles fixed in geographic location on leaflet map but constant size
You may (justifiably) ask yourself why we would want to do this with d3.js when Leaflet could
do the same job with a marker? The answer is that as cool as leaflet.jss markers are, d3 elements
have a wider range of features that make their use advantageous in some situations. For instance
if you want to animate or rotate the icons or dynamically adjust some of their attributes, d3.js
would have a greater scope for adjustments.
341
d3.json("circles.json", function(collection) {
// Add a LatLng object to each item in the dataset
collection.objects.forEach(function(d) {
d.LatLng = new L.LatLng(d.circle.coordinates[0],
d.circle.coordinates[1]
})
342
There is also an associated json data file (called circles.json) that has the following contents;
{"objects":[
{"circle":{"coordinates":[-41.28,174.77]}},
{"circle":{"coordinates":[-41.29,174.76]}},
{"circle":{"coordinates":[-41.30,174.79]}},
{"circle":{"coordinates":[-41.27,174.80]}},
{"circle":{"coordinates":[-41.29,174.78]}}
]}
The full code and a live example are available online at bl.ocks.org or GitHub. They are also
available as the files leaflet-d3-linked.html and circles.json as a separate download with D3
Tips and Tricks. A a copy of all the files that appear in the book can be downloaded (in a zip file)
when you download the book from Leanpub
While I will explain the code below, as with the previous example (which is similar, but different)
please be aware that I will gloss over some of the simpler sections that are covered in other
http://bl.ocks.org/d3noob/9267535
https://gist.github.com/d3noob/9267535
https://leanpub.com/D3-Tips-and-Tricks
343
sections of either books and will instead focus on the portions that are important to understand
the combination of d3 and leaflet.
Our code begins by setting up the html document in a fairly standard way.
<!DOCTYPE html>
<html>
<head>
<title>d3.js with leaflet.js</title>
<link
rel="stylesheet"
href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"
/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script
src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js">
</script>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
Here were getting some css styling and loading our leaflet.js / d3.js libraries. The only
configuration item is where we set up the size of the map (in the <div> section and as part
of the map div).
Then we break into the JavaScript code. The first thing we do is to project our Leaflet map;
var map = L.map('map').setView([-41.2858, 174.7868], 13);
mapLink =
'<a href="http://openstreetmap.org">OpenStreetMap</a>';
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© ' + mapLink + ' Contributors',
maxZoom: 18,
}).addTo(map);
This is exactly the same as we have done in any of the simple map explanations in Leaflet Tips
and Tricks and in this case we are using the OpenStreetMap tiles.
Then we start on the d3.js part of the code.
Firstly the Leaflet map is initiated as SVG using map._initPathRoot().
https://leanpub.com/leaflet-tips-and-tricks/
344
Then we select the svg layer and append a g element to give a common reference point g =
svg.append("g").
Then we load the json file with the coordinates for the circles;
d3.json("circles.json", function(collection) {
Then for each of the coordinates in the objects section of the json data we declare a new latitude
/ longitude pair from the associated coordinates;
collection.objects.forEach(function(d) {
d.LatLng = new L.LatLng(d.circle.coordinates[0],
d.circle.coordinates[1])
})
Then we use a simple d3.js routine to add and place our circles based on the coordinates of each
of our objects.
var feature = g.selectAll("circle")
.data(collection.objects)
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "red")
.attr("r", 20);
We declare each as a feature and add a bit of styling just to make them stand out.
The last main part of our JavaScript makes sure that when our view of what were looking at
changes (we zoom or pan) that our d3 elements change as well;
map.on("viewreset", update);
update();
Obviously when our view changes we call the function update. Its the job of the update function
to ensure that whenever the leaflet layer moves, the SVG layer with the d3.js elements follows
and the points that designate the locations of those objects move appropriately;
345
function update() {
feature.attr("transform",
function(d) {
return "translate("+
map.latLngToLayerPoint(d.LatLng).x +","+
map.latLngToLayerPoint(d.LatLng).y +")";
}
)
}
Here we are using the transform function on each feature to adjust the coordinates on our
LatLng coordinates. We only need to adjust our coordinates since the size, shape, rotation and
any other attribute or style is dictated by the objects themselves.
And there we have it!
d3.js circles fixed in geographic location on leaflet map but constant size
Introduction to Crossfilter
Crossfilter is a JavaScript library for exploring large datasets that include many variables in the
browser. It supports extremely fast interactions with concurrent views and was built to power
analytics for Square Register so that online merchants can slice and dice their payment history
fluidly. It was developed for Square by (amongst other people) the ever tireless Mike Bostock
and was released under the Apache Licence.
Crossfilter provides a map-reduce function to data using dimensions and groups. Map-reduce
is an interesting concept itself and its useful to understand it in a basic form to understand
crossfilter better.
https://squareup.com/register
https://squareup.com/
http://www.apache.org/licenses/LICENSE-2.0.html
347
Map-reduce
Wikipedia tells us that MapReduce is a programming model for processing large data sets with
a parallel, distributed algorithm on a cluster. Loosely translated into language I can understand,
I think of a large data set having one dimension mapped or loaded into memory ready to be
worked on. In practical terms, this could be an individual column of data from a larger group of
information. This column of data has key values which we can define as being distinct. In the
case of the data below, this could be earthquake magnitudes.
The reduce function then takes that dimension and reduces it by grouping it according to a
specific aspect. For instance in the example above we may want to group each unique value
of magnitude (by counting how many occurrences of each there are) to know how many
earthquakes of a specific magnitude have taken place. Leaving us with a very specific subset
of our data.
Magnitude
2.6
2.7
2.8
2.9
3.0
3.1
3.2
3.3
Count
63
134
292
299
378
351
403
455
https://en.wikipedia.org/wiki/MapReduce
348
3.4
3.5
512
688
Please dont think that this is the sum total of information you need to know to be the
master of map-reduce. This is a ridiculously simplistic view which is only intended to
supply enough information to get you familiar with the way that we will use crossfilter
later :-).
Here we are presented with five separate views of a data set that represents flight records
demonstrating airline on-time performance. There are 231,083 flight records in the database
being used, so getting that rendered in a web page is no small feat in itself.
The bottom view is a table showing data for individual flights. The top, left view is of the number
of flights that occur at a specific hour of the day.
http://square.github.io/crossfilter/
349
The top, middle graph shows the amount of delay for flights grouped in 10 minute intervals.
The top, right graph shows the distance covered by each flight grouped in 50 mile chunks.
The wider bar graph in the second row shows the number of flights per day.
This particular graph is the first to give a hint at how cool this visualization really is, because it
includes a section in the middle of the graph which is selected with handles on either side of
the selection. You can move these handles with a mouse and as a result you will find all the data
represented in the other graphs adjusting dynamically to follow your selection.
This same feature is available in all the graphs. So you are able to filter dynamically and have the
results presented virtually instantaneously. This is where you can start to have fun and discover
things that might not be immediately obvious.
350
For instance, if we select only the flights that arrived late, we can see a marked skew in the time
of day. Does this mean that flights that are delayed will typically be in the late evening?
So this is why tools like crossfilter are cool. All we need to do now is learn how to make them
ourselves :-).
Introduction to dc.js
Why, if weve just explored the benefits of crossfilter are we now introducing a completely
different JavaScript library (dc.js)?
Well, crossfilter isnt a library thats designed to draw graphs. Its designed to manipulate data.
D3.js is a library thats designed to manipulate graphical objects (and more) on a web page. The
two of them will work really well together, but the barrier to getting data onto a web page can
be slightly daunting because the combination of two non-trivial technologies can be difficult to
achieve.
This is where dc.js comes in. It was developed by Nick Qi Zhu and the first version was
released on the 7th of July 2012.
Dc.js is designed to be an enabler for both libraries. Taking the power of crossfilters data
manipulation capabilities and integrating the graphical capabilities of d3.js.
It is designed to provide access to a range of different chart types in a relatively easy to use
fashion. It is more limited in the range of options available for graphical design in this respect
than d3.js, but the simplicity that it provides for creating pages using crossfiltered data is a real
benefit if youre anything like me and need all the help you can get.
The different (generic) types of chart that dc.js supports are
Bar Chart
Pie Chart
Row Chart
http://nickqizhu.github.io/dc.js/
https://github.com/NickQiZhu
351
Line Chart
Bubble Chart
Geo Choropleth Chart
Data Table
All these examples come with a range of options which we will cover in greater depth in later
sections.
My initial sources of information for developing the examples here came primarily from;
Nick Zhus examples
Rusty Klophaus blog post on crossfilter
Eamonn OLoughlins blog post on dc.js
Bar Chart
This is a standard bar chart.
Pie Chart
This is a standard pie chart. The examples below are from one of Nick Zhus dc.js example
pages.
352
Row Chart
The row chart is a horizontal version of a bar chart, but with the ability to represent discrete
values and to select them for filtering by clicking on them.
Line Chart
Standard line chart.
Bubble Chart
The bubble chart is a derivative of a scatter plot with control over x axis position, y axis position,
bubble radius and colour.
353
Data Table
A data table is a simple table made up of data elements derived from the information loaded.
http://nickqizhu.github.io/dc.js/vc/
354
355
src='js/d3.js' type='text/javascript'></script>
src='js/crossfilter.js' type='text/javascript'></script>
src='js/dc.js' type='text/javascript'></script>
src='js/jquery-1.9.1.min.js' type='text/javascript'></script>
src='js/bootstrap.min.js' type='text/javascript'></script>
<div class='row'>
<div class='span12'>
<table class='table table-hover' id='dc-table-graph'>
<thead>
<tr class='header'>
<th>DTG</th>
<th>Lat</th>
<th>Long</th>
<th>Depth</th>
<th>Magnitude</th>
<th>Google Map</th>
<th>OSM Map</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<script>
// Create the dc.js chart objects & link to div
var dataTable = dc.dataTable("#dc-table-graph");
// load data from a csv file
d3.csv("data/quakes.csv", function (data) {
// format our data
var dtgFormat = d3.time.format("%Y-%m-%dT%H:%M:%S");
data.forEach(function(d) {
d.dtg
= dtgFormat.parse(d.origintime.substr(0,19));
d.lat
= +d.latitude;
d.long = +d.longitude;
d.mag
= d3.round(+d.magnitude,1);
d.depth = d3.round(+d.depth,0);
});
// Run the data through crossfilter and load our 'facts'
var facts = crossfilter(data);
// Create dataTable dimension
var timeDimension = facts.dimension(function (d) {
return d.dtg;
});
356
357
The first part of the code starts the html file and inside the <head> segment loads our JavaScript
and css files
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>dc.js Experiment</title>
<script
<script
<script
<script
src='js/d3.js' type='text/javascript'></script>
src='js/crossfilter.js' type='text/javascript'></script>
src='js/dc.js' type='text/javascript'></script>
src='js/jquery-1.9.1.min.js' type='text/javascript'></script>
358
Its worth noting that the order of loading the files is important. The
jquery-1.9.1.min.js file must be loaded before the bootstrap.min.js file or
it just wont work.
From here we move into the section where we set up our page to load our bootstrap grid layout
for the table.
<div class='container' style='font: 12px sans-serif;'>
<div class='row'>
<div class='span12'>
<table class='table table-hover' id='dc-table-graph'>
<thead>
<tr class='header'>
<th>DTG</th>
<th>Lat</th>
<th>Long</th>
<th>Depth</th>
<th>Magnitude</th>
<th>Google Map</th>
<th>OSM Map</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
It might look a little complicated, but if you have a look through the bootstrap chapter (where
we cover using the bootstrap grid layout), you will find it no problem at all.
The important features to note are that we have declared an ID selector for our table id='dc-table-graph'
and we have set a series of headers for the table; DTG, Lat, Long, Depth, Magnitude, Google Map
and OSM Map.
We have also included some bootstrap styling for the table by including the class='table
table-hover' portion of the code. With that styling included our table looks like this;
359
We will be adding to this grid layout section as we add in charts which will want their own
allocated space on our page.
The next section of the file starts our JavaScript and declares our variables for our charts.
// Create the dc.js chart objects & link to div
var dataTable = dc.dataTable("#dc-table-graph");
The first line assigns the variable dataTable to the dc.js dataTable chart type (var dataTable =
dc.dataTable("#dc-table-graph");) and assigns the chart to the ID selector dc-table-graph.
Then we get into the d3.js.
360
We load our csv file with the line d3.csv("data/quakes.csv", function (data) {. I have
deliberately left this file in its raw form as received from Geonet. Its format looks a little like
this;
FID,publicid,origintime,longitude,latitude,depth,magnitude,magnitudetype,statu\
s,phases,type,agency,updatetime,origin_geom
quake.2013p550753,2013p550753,2013-07-23T18:41:11.707,174.4298,-41.5313,7.9883\
,2.2425,M,automatic,27,,WEL(GNS_Primary),2013-07-23T18:43:15.672,POINT (174.42\
978 -41.531299)
quake.2013p550747,2013p550747,2013-07-23T18:38:02.481,174.414,-41.5181,11.6797\
,1.7892,M,automatic,11,,WEL(GNS_Primary),2013-07-23T18:39:25.37,POINT (174.413\
98 -41.518114)
quake.2013p550725,2013p550725,2013-07-23T18:26:30.229,175.5516,-40.0264,8.75,3\
.4562,M,automatic,21,,WEL(GNS_Primary),2013-07-23T18:29:46.305,POINT (175.5515\
5 -40.026412)
We then declare a small function that will format our time correctly (var dtgFormat =
d3.time.format("%Y-%m-%dT%H:%M:%S");). This follows exactly the same procedure we took
when creating our very first simple line graph at the start of the book.
However, there is a slight twist Observant readers will notice that while we have
a function that resolves a date/time that is formatted with year, month, day, hour,
minute and second values, I dont include an allowance for the fractions of seconds
that appear in the csv file. Well spotted. The reason for this is that in spite of initially
including this formatting, I found it caused some behaviour that I couldnt explain,
so I reverted to cheating and you will note that in the next section when I format
the values from the csv file, I truncate the date/time value to the first 19 characters
(d.origintime.substr(0,19)). This solved my problem by chopping off the fractions
of a second (admittedly without actually solving the underlying issue) and I moved on
with my life.
While were on the subject, observant readers will have noticed that the format of the date / time
that appears in the table are (how to put this kindly.), not what came out of the csv file.
361
If you want to put this in a different format we can employ the same technique we used when
formatting time figures in the section that dealt with tables. All we need to do is to assign a new
variable for our correctly formatted time in the forEach loop. and then call that variable when
displaying the table values.
The following code will create a date / time string in the format yyyy-mm-dd hh:mm:ss with a
variable name dtg1 (put this in the forEach loop).
d.dtg1
Then, when your code calls the values for the table, instead of the line that says;
function(d) { return d.dtg; },
As mentioned, the next section goes through each of the records and formats them correctly.
The date/time gets formatted, the latitude and longitude are declared as numerical values (if
they werent already) and the magnitude and depth values are rounded to make the process of
grouping them simpler.
data.forEach(function(d) {
d.dtg
= dtgFormat.parse(d.origintime.substr(0,19));
= +d.latitude;
d.lat
d.long = +d.longitude;
= d3.round(+d.magnitude,1);
d.mag
d.depth = d3.round(+d.depth,0);
});
The next section in our code sets up the dimensions and groupings for the dc.js chart type and
crossfilter functions.
362
We load all of our data into crossfilter (var facts = crossfilter(data);) and give it the name
facts.
Then we create a dimension from our data (facts) of the date/time values.
var timeDimension = facts.dimension(function (d) {
return d.dtg;
});
The last major chunk of code is the piece that configures our data table.
dataTable.width(960).height(800)
.dimension(timeDimension)
.group(function(d) { return "Earthquake Table"
})
.size(10)
.columns([
function(d) { return d.dtg; },
function(d) { return d.lat; },
function(d) { return d.long; },
function(d) { return d.depth; },
function(d) { return d.mag; },
function(d) { return '<a href=\"http://maps.google.com/maps?z=12&t=m&q=loc:\
' + d.lat + '+' + d.long +"\" target=\"_blank\">Google Map</a>"},
function(d) { return '<a href=\"http://www.openstreetmap.org/?mlat=' + d.la\
t + '&mlon=' + d.long +'&zoom=12'+ "\" target=\"_blank\"> OSM Map</a>"}
])
.sortBy(function(d){ return d.dtg; })
.order(d3.ascending);
Firstly the width and height are declared (dataTable.width(960).height(800)). Then the
dimension of the data that will be used is declared (.dimension(timeDimension)).
Separate sections of the table can have a header applied. In this case the entire table is
given the grouping Earthquake Table (.group(function(d) { return "Earthquake
Table"})), but several examples online give the date.
The .size(10) line sets the maximum number of lines of the table to be displayed to 10.
363
Then we have the block of code that sets what data appears in which columns. It should be noted
that this matches up with the headers that were declared in the earlier section of the code where
the divs for the table were laid out.
The portion of this block that has a little bit of fancy are the two columns that set links that
allow a user to click on the designation Google Map or OSM Map and have the browser open
a new window containing a Google or Open Street Map (OSM) map with a marker designating
the location of the quake. I wont mention too much about how the links are made up other than
to say that they are pretty much a combination of the latitude, longitude and zoom level for both.
Please check out the code for more.
Lastly we sort by the date/time value (.sortBy(function(d){ return d.dtg; })) in ascending
order (.order(d3.ascending);).
The final part of our JavaScript renders all our charts (dc.renderAll();) and then closes off the
initial d3.csv call.
// Render the Charts
dc.renderAll();
});
The final part of our code simply closes off the <script>, <body> and <html> tags.
There we have it. The template for starting to play with different crossfiltered dc.js charts.
364
Well work through adding the chart in stages (and this should work for subsequent charts).
Firstly well organise a position for our chart on the page using the bootstrap grid set-up. Then
well name our chart and assign it a chart type. Then well create any required dimension and
grouping and finally well configure the parameters for the chart. Sounds simple right?
1.
2.
3.
4.
We add in a new row that has two span6s in it (remembering our total is a span of 12 (see the
section on bootstrap layout if its a bit unfamiliar)).
365
<div class='row'>
<div class='span6' id='dc-magnitude-chart'>
<h4>Events by Magnitude</h4>
</div>
<div class='span6' id='blank'>
<h4>Blank</h4>
</div>
</div>
Weve given the first span6 an ID selector of dc-magnitude-chart. So when we we assign our
chart that selector, it will automatically appear in that position. Weve also put a simple title in
place (<h4>Events by Magnitude</h4>). The second span6 is set as blank for the time being
(well put another bar chart in it later).
All done.
This dimension (magValue) has been set and now has, as its index, each unique magnitude that
is seen in the database. This is essentially defining the values on the x axis for our bar chart.
Then we want to group the data by counting the number of events of each magnitude.
366
This piece of code (which should go directly under the magValue dimension portion), groups
(.group()) by counting (.reduceCount) all of the magnitude values (function(d) { return
d.mag; })) and assigns it to the magValueGroupCount variable. This has essentially defined the
values for the y axis of our bar chart (the number of times each magnitude occurs).
That should be it. With the addition of this portion of the code, you should have a functioning
visualization that can be filtered dynamically. Just check to make sure that everything is working
properly and well go through some of the configuration options to see what they do.
Your web page should look a little like this;
367
The configuration options start by declaring the name of the chart (magnitudeChart) and setting
the height and width of the chart.
magnitudeChart.width(480)
.height(150)
In the case of our example I have selected the width based on the default size for a span6 grid
segment in bootstrap and adjusted the height to make it look suitable.
Then we have our margins set up.
.margins({top: 10, right: 10, bottom: 20, left: 40})
Nothing too surprising there although the left margin is slightly larger to allow for larger values
on the y axis to be represented without them getting clipped.
Then we define which dimension and grouping we will use.
.dimension(magValue)
.group(magValueGroupCount)
I like to think of this section as the .dimension declaration being the x axis and the .group
declaration being the y axis. This just helps me get the graph straight in my head before its
plotted.
The .transitionDuration setting defines the length of time that any change takes to be applied
to the chart as it adjusts.
.transitionDuration(500)
Then we ensure that the bar for the bar graph is centred on the ticks on the x axis.
368
.centerBar(true)
Without this (true is not the default), the graph will look slightly odd.
The setting of the gap between the bars is accomplished with the following setting;
.gap(65)
I will admit that I still dont quite understand how this setting works exactly, but I can get it to
do what I want with a little trial and error.
For instance, I would expect that .gap(2) would have the effect of producing a gap of 2 pixels
between the bars. But this would be the result for our graph if I have that set.
If you select a portion of the graph you will see some strange things going on. That appears to
be as a result of the bars being too wide for the graph.
Setting the gap for a bar graph is a pretty tricky thing to do (programmatically), and I can see
why it would throw some strange results. The way around this and the way to find the ideal
.gap setting is to set the .gap value high and then reduce it till its right.
For instance, if we set it to 100 (.gap(100)) we will get the following result.
369
Then we just keep backing the values off till we reach an acceptable chart on the screen.
In the case of our example, its .gap(65).
I have added in the next setting more because I want you to know it exists, rather than wanting
to use it in this example.
.filter([3, 5])
Setting the .filter configuration will load the graph with a portion of it pre-selected. If you
omit this parameter, the entire graph is selected by default. In most cases that I can think of, that
is what I would start with.
We can set the range of values presented in our graph by defining the domain (in the same way
as for d3.js).
.x(d3.scale.linear().domain([0.5, 7.5]))
The next parameter sets the y axis to adjust dynamically as the filtered data is returned.
370
.elasticY(true)
The final parameter that we set is to format the values on the x axis.
.xAxis().tickFormat();
And thats it! A bar graph added to your visualization with full dynamic control.
We could have just as easily summed the magnitude values instead of counting them by using
.reduceSum instead of .reduceCount. This has the effect of increasing the value on the y axis (as
the sum of the magnitudes would have been greater than the count) like so
The reason I mention it is that summing the numeric value would be useful in many circumstances (file size or packet size or similar).
Assign type
var depthChart = dc.barChart("#dc-depth-chart");
371
depthChart.width(480)
.height(150)
.margins({top: 10, right: 10, bottom: 20, left: 40})
.dimension(depthValue)
.group(depthValueGroup)
.transitionDuration(500)
.centerBar(true)
.gap(1)
.x(d3.scale.linear().domain([0, 100]))
.elasticY(true)
.xAxis().tickFormat(function(v) {return v;});
372
373
Just as with the bar chart, well work through adding the chart in the following stages.
1.
2.
3.
4.
374
<div class='row'>
<div class='span12' id='dc-time-chart'>
<h4>Events per hour</h4>
</div>
</div>
Weve given it an ID selector of dc-time-chart. So when we assign our chart that selector, it will
automatically appear in that position. Weve also put another simple title in place (<h4>Events
per hour</h4>).
Nice.
This dimension (volumeByHour) uses the same facts data, but when the key values are returned
(return d3.time.hour(d.dtg);) we are going to return the information by hours. This is
essentially defining the resolution of the values on the x axis for our line chart.
Then we want to group the data by counting the number of events of for each hour.
375
This piece of code (which should go directly under the volumeByHour dimension portion) groups
(.group()) by counting (.reduceCount) all of the magnitude values (function(d) { return
d.dtg; })) and assigns it to the volumeByHourGroup variable. This has defined the values for the
y axis of our line chart (the number of events we see in a given hour).
That should be it. With the addition of this portion of the code, you should have a functioning
visualization that can be filtered dynamically. Just check to make sure that everything is working
properly and well go through some of the configuration options to see what they do.
To start with, your page should look something like this;
http://nickqizhu.github.io/dc.js/
https://github.com/NickQiZhu/dc.js/wiki/API
376
The configuration options start by declaring the name of the chart (timeChart) and setting the
height and width of the chart.
timeChart.width(960)
.height(150)
In the case of our example I have selected the width based on the default size for a span12 grid
segment in bootstrap and adjusted the height to make it look suitable.
Then we have our margins set up.
.margins({top: 10, right: 10, bottom: 20, left: 40})
Nothing too surprising there although the left margin is slightly larger to allow for larger values
on the y axis to be represented without them getting clipped (not strictly for this example, but
its a handy default).
Then we define which dimension and grouping we will use.
.dimension(volumeByHour)
.group(volumeByHourGroup)
Think of the .dimension declaration being the x axis and the .group declaration being the y
axis.
The .transitionDuration setting defines the length of time that any change takes to be applied
to the chart as it adjusts.
377
.transitionDuration(500)
We can set the y axis to dynamically adjust when the number of events are filtered by selections
on any of the other charts.
.elasticY(true)
For instance if we select only earthquakes with a magnitude between 4 and 5, our line chart will
have a maximum value on the y axis of 7 events;
However, if we select all the earthquakes, the y axis will dynamically adjust to over 30.
Since the line chart has an x axis which is made of date/time values, we set our scale and domain
using the d3.time.scale declaration.
.x(d3.time.scale().domain([new Date(2013, 6, 18), new Date(2013, 6, 24)]))
This is hard coded for our date range, but a smarter method would be to have the scale adjust to
suit your range of date/time values automatically with the following line;
378
Using the d3.extent function means that our line graph of time now spans the exact range of
our data values on the x axis (note that the time scale now starts just before the 18th and ends
when our data ends).
We need to turn off the .brushOn feature (.brushOn(false)) that allows for selection and add
in the .title function as follows;
379
// time graph
timeChart.width(960)
.height(150)
.margins({top: 10, right: 10, bottom: 20, left: 40})
.dimension(volumeByHour)
.group(volumeByHourGroup)
.transitionDuration(500)
.brushOn(false)
.title(function(d){
return d.data.key
+ "\nNumber of Events: " + d.data.value;
})
.elasticY(true)
.x(d3.time.scale().domain([new Date(2013, 6, 18), new Date(2013, 6, 24)]))
.xAxis();
As we can see, the tooltip is using the default time format for the script from our key value
(on the x axis), and as a result, the representation of the date / time is quite long winded. We
can adapt this to a format of our choosing by calling a time formatting function similar to the
following;
var dtgFormat2 = d3.time.format("%a %e %b %H:%M");
This line could ideally go after the other time formatting function (dtgFormat) that occurs earlier
in the script. The formatting its introducing can be found in the d3.js wiki, but in short it
returns the date / time formatted as abbreviated weekday name, day of the month as a decimal
number, abbreviated month name and 24 hour clock hour:minute.
With our function in place, the .title. call from our line chart configuration code would now
look like this;
https://github.com/mbostock/d3/wiki/Time-Formatting#wiki-format
380
.title(function(d){
return dtgFormat2(d.data.key)
+ "\nNumber of Events: " + d.data.value;
})
We also add in the number of the events from the y axis (d.data.value), separated with a new
line character (\n) and some appropriate text.
381
Selecting a Row
382
You can select an individual row from your chart and all the other rows reflect the selection.
Go ahead and select other combinations of more than one row if you want. Welcome to data
immersion!
Just as with the previous chart examples, well work through adding the chart in the following
stages.
1.
2.
3.
4.
Weve given it an ID selector of dc-dayweek-chart. So when we assign our chart that selector, it
will automatically appear in that position. Weve also put another simple title in place (<h4>Day
of the Week</h4>).
The additional two span4s have been left blank.
383
This dimension (dayOfWeek) uses the same facts data, but when we return our key values we
are going to return them as a combination of their numerical order (0 = Sunday etc) and their
abbreviation (Sun = Sunday etc). This is essentially defining the categories of the values on the
y axis for our row chart.
384
The code snippet looks a little strange, but think of it as extracting the numerical representation
of the day of the week from our data (var day = d.dtg.getDay();) and then matching each
number with an appropriate label (0 = 0.Sun, 1 = 1.Mon etc). Its these labels that are now our
key values in our dimension.
Then we want to group the data by using the default action of the .group() function to count
the number of events for each day of the week.
var dayOfWeekGroup = dayOfWeek.group();
That should get you working. With the addition of this portion of the code, you should have a
functioning visualization that can be filtered dynamically by clicking on the appropriate day of
the week in your row chart. Just check to make sure that everything is working properly and
well go through some of the configuration options to see what they do.
To start with, your page should look something like this;
http://nickqizhu.github.io/dc.js/
https://github.com/NickQiZhu/dc.js/wiki/API
385
The configuration options start by declaring the name of the chart (dayOfWeekChart) and setting
the height and width of the chart.
dayOfWeekChart.width(300)
.height(220)
In the case of our example I have selected the width based on the default size for a span4 grid
segment in bootstrap and adjusted the height to make it look suitable.
Then we have our margins set up.
.margins({top: 5, left: 10, right: 10, bottom: 20})
Nothing too surprising there although I did reduce the top margin slightly more than I thought
I would need. You can be the judge for your own charts.
Then we define which dimension and grouping we will use.
386
.dimension(dayOfWeek)
.group(dayOfWeekGroup)
For a row chart, think of the .dimension declaration being the y axis and the .group declaration
being the x axis (the opposite to the previous charts).
We can set the range of colours to use one of the standard palettes.
.colors(d3.scale.category10())
Then we add the labels to our categories by splitting the key values (remember 0.Sun, 1.Mon etc)
at the decimal point and returning the second part of the split value (which is the Sun, Mon part)
as the label.
.label(function (d){
return d.key.split(".")[1];
})
A cool way to prove this is to change the variable that returns the label to use the 1st part of the
split value buy using a [0] instead of a [1] with code like this;
.label(function (d){
return d.key.split(".")[0];
})
The next line in the configuration adds a tool tip to our row chart using the value when the
mouse hovers over the appropriate bar.
http://www.schneidy.com/Tutorials/ColorTutorial.html
387
.title(function(d){return d.value;})
We can set the x axis to dynamically adjust when the number of events are filtered by selections
on any of the other charts using the following configuration line.
.elasticX(true)
For instance if we select a subset of the earthquakes using our time / line chart, our row chart
will have a corresponding selection of the appropriate days and the x axis will alter accordingly.
388
389
Good news! The pie chart shares the same cool feature as the row chart
Click on one of the pie segments
390
The code that sets up that row should now look like this;
<div class='row'>
<div class='span4' id='dc-dayweek-chart'>
<h4>Day of the Week</h4>
</div>
<div class='span4' id='dc-island-chart'>
<h4>North or South Island</h4>
</div>
<div class='span4' id='blank2'>
<h4>Blank 2</h4>
</div>
</div>
Weve given it an ID selector of dc-island-chart. So when we assign our chart that selector, it
will automatically appear in that position. Weve also put another simple title in place (<h4>North
or South Island</h4>).
The last span4 is still blank.
391
This dimension (islands) uses the same facts data, but when we return our key values we are
going to return them as either North or South. To do this we employ a simple if statement
with a little logic. These are the only two slices for our pie chart.
Then we want to group the data by using the default action of the .group() function to count
the number of events for each day of the week.
var islandsGroup = islands.group();
That should get the chart working. With the addition of this portion of the code, you should
have a functioning visualization that can be filtered dynamically by clicking on the appropriate
island in your pie chart. Just check to make sure that everything is working properly and well
go through some of the configuration options to see what they do.
To start with, your page should look something like this;
392
The configuration options start by declaring the name of the chart (islandChart) and setting the
height and width of the chart.
islandChart.width(250)
.height(220)
In the case of our example I have selected the width based on the default size for a span4 grid
segment in bootstrap and adjusted the height to make it look suitable alongside the row chart.
Then we set up our inner and outer radii for our pie.
.radius(100)
.innerRadius(30)
This is fairly self explanatory, but by all means adjust away to make sure the chart suits your
visualization.
Then we define which dimension and grouping we will use.
393
.dimension(islands)
.group(islandsGroup)
For a pie chart, the .dimension declaration is the discrete values that make up each segment of
the pie and the .group declaration is the size of the pie.
The final line in the configuration adds a tool tip to our pie chart using the value when the mouse
hovers over the appropriate slice.
.title(function(d){return d.value;})
394
Resetting filters
Once you have made selections on some of your data dimensions, often you will want to reset
those selections to return to a stable state.
For example, when selecting different days to display in the row chart, if you have three days
selected as so
to return to the default setting where all the days are selected can be a bit of a pain.
Instead, we can use a dc.js reset feature where a reset label is generated to allow us revert to
the starting condition.
There is a simple way to enable this feature, but well take an additional few steps to make it
look slightly better (and to learn some new tricks).
In the simplest method, this feature simply involves adding in the following code to the section
where we add in the rows and spans when setting out our layout.
<a class="reset"
href="javascript:dayOfWeekChart.filterAll();dc.redrawAll();"
style="display: none;">
reset
</a>
In the case of our example row chart, that would then look a bit like this;
395
The additional code adds in a link (thats the <a> tags) with a specific class that designates
its function (the class="reset" part (this is what will let dc.js know what to do)). The
link action (href="javascript:dayOfWeekChart.filterAll();dc.redrawAll();") provides the
instructions on what to do when the reset link is clicked on (in this case, we remove all the filters
and redraw the dayOfWeekChart chart). Then theres a nice touch to not display the word reset
when the page first loads (style="display: none;") before finally printing the word reset on
the page.
The end result (when a day of the week is selected) looks like this;
You can now click on the reset link and the chart will revert to the default setting of all days
selected.
396
The first thing we want to do is to get the reset label onto the same line as our Day of the
Week heading.
This is simply done by ensuring that the <a> section is inside the <h4> section. The code should
therefore look like this;
<div class='span4' id='dc-dayweek-chart'>
<h4>Day of the Week
<a class="reset"
href="javascript:dayOfWeekChart.filterAll();dc.redrawAll();"
style="display: none;">
reset
</a>
</h4>
</div>
(Notice how the code layout shows the <a> code nested inside the <h4> section?)
The result on the web page now looks like this when a day is selected;
Thats a good start and certainly more acceptable, but the styling for the reset label still looks
a bit bold and BIG. We can do better than that.
What well do is place our <a> tag information inside a <span> tag (this is the type of tag to use
for in-line elements). Then well set a CSS style in our <stlye> area to make any text that is
inside a <span> which is inside a <h4> appear with formatting that makes it not bold and smaller
in size.
First of all we place the <a> tag into a <span> container like so;
397
Then we create a section at the start of our file (under the <style type="text/css"></style>
line looks like the right place) that declares the styling for our h4 span text. It should look like
this;
<style>
h4 span {
font-size:14px;
font-weight:normal;
}
</style>
That tells our web page that any h4, span labelled text should be 14px in size and not bold (or
normal).
The end result when you now have a day of the week selected looks like this;
Nicer Looking Reset Link for the Row Chart on the Same Line
398
associated data would be downloaded again. So just resetting everything in the browser is a
good feature.
Again dc.js has got our back.
This feature is treated like a separate chart in itself, so it has a dimension and group and a section
to draw the chart (not that its a chart, but Im sure you get the idea). Its executed slightly
differently, but its not too tricky.
What were going to aim to do is provide our page with a title and add some nice dc.js trickery
alongside that looks like this;
The trickery shows us the number of selected records accompanied with the total number of
records and gives us the option to reset all the selected charts so that all the records are selected.
There are 4 pieces of code that we will add to accomplish this task. We wont add them from top
to bottom, because it makes slightly more sense to explain them in a different order.
First of all we will add the block of code that declares the variable that includes all of our data
values (facts).
var all = facts.groupAll();
This piece of code should go soon after the line that initialises the crossfilter process (var facts
= crossfilter(data);).
Then we will include a section of code that dimensions and counts all of our facts. It also anchors
the values to the dc-data-count ID Selector that we will set up in a moment.
// count all the facts
dc.dataCount(".dc-data-count")
.dimension(facts)
.group(all);
This block of code belongs in the section that sets up our charts, although you could be forgiven
for thinking that it kind of straddles more than one section.
The next section well add will be our title along with the count and reset information. It looks
like this;
399
This block needs to go at the top of our area in the file where the layout of the portions of
the web page are being set out. Put it directly under the outermost container div line (<div
class='container' style='font: 12px sans-serif;'>).
It places a <h2> heading with the text New Zealand Earthquakes and then places, in-line with
this, five additional pieces. The first is a count of the filtered facts via
<span class="filter-count"></span>
Then there is the text selected out of followed by a count of the total number of facts via
<span class="total-count"></span>
The some more text records | and then another JavaScript call (as a link) that allows us to reset
all the chart elements via
<a href="javascript:dc.filterAll(); dc.renderAll();">Reset All</a>
This is all well and good, but the formatting will look a bit strange (like the following).
This tells us that we need to apply some styling to the elements alongside the title. We can do this
with the following CSS elements which can go into the <style> block with the one we added
earlier for the other reset block.
400
h2 {
float: right;
}
h2 span {
font-size:14px;
font-weight:normal;
}
These will allow the <h2> heading to be left justified and will reduce the size of the in-line span
and remove the bold formatting.
Et viola!
What is Bootstrap?
Twitter Bootstrap is a free collection of tools for creating websites and web applications.
It contains HTML and CSS based design templates for typography, forms, buttons, charts,
navigation and other interface components, as well as optional JavaScript extensions.
Bootstrap was developed by Mark Otto and Jacob Thornton at Twitter as a framework to encourage consistency across internal tools. The word framework is probably the best descriptive
term, since its purpose is to provide structure to content. Perhaps in a similar way that d3.js
provides structure to data.
Some of Bootstraps most important features include;
A layout grid
Interface components
402
Layout grid
A default standard 940 pixel width grid layout which allows you to quickly arrange a page
structure. This allows you to plan and implement what youre going to place on the page with
a minimum of fuss. You can change any of the pre-set options if you wish and you can also
implement a fluid row option where bootstrap will dynamically size a columns width using a
percentage instead of a fixed pixel value.
Its this feature that first attracted me to using Bootstrap and while I may be using a complex
tool for a simple task, it does that task very well.
403
Interface components
A large number of interface components are also provided. These include standard buttons,
labels, pre-formatted warning and system messages, navigation controls, wizard controls,
pagination, and breadcrumbs.
There is a dizzying array of options available for web designers and while I encourage you to
use them, I cant promise to explain the nuances of their use, since Im a humble journeyman in
this world :-).
404
Bootstrap Download
You will need to copy the bootstrap.js file (or the minimised version (bootstrap.min.js)) to
a place where it can be reached and loaded by your script. While youre there, you will need
to include a line to load the jquery.js file (which is a dependency of Bootstrap (not that it gets
talked about much)) The following two lines, included with the line that loads d3.js, would do
the job nicely (assuming that youve copied the bootstrap.min.js file into the js directory);
<script src="http://code.jquery.com/jquery.js"></script>
<script src="js/bootstrap.min.js"></script>
Make sure that the jquery line comes before the bootstrap line, because it wont work
the other way round.
You will also need to copy the bootstrap.css (or the minimised version (bootstrap.min.css)) to
a place where it can be reached and loaded by your script. The following lines show it being
loaded from the css directory with the line that loads the script in the <head> section.
http://twitter.github.io/bootstrap/customize.html
http://twitter.github.io/bootstrap/getting-started.html
http://twitter.github.io/bootstrap/getting-started.html
405
<head>
<link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
That should be all thats required! Of course as I mentioned earlier, there are plenty of other
plug-in scripts that could be loaded to do fancy things with your web page, but were going to
try and keep things simple.
406
407
.call(yAxis);
});
For simplicity, I have generated an example you can use as a starting point on bl.ocks.org here.
The differences from the original simple graph example are;
The graphs are slightly smaller (to make it easier to display the graphs as they move about).
I have used *.csv files for the data and there are two different data files so that they look
different and we can differentiate between the graphs.
Most importantly, I have declared the two charts with different variable names (one as
chart1 and the other as chart2).
The different variable names are important, because if you leave them with the same identifier,
the web page decides that what youre trying to do is to put all your drawing data into the same
space. The end result is two graphs trying to occupy the same space and looks a bit like this
The example with the correct (different) variable labels should look a little like this
408
This has the effect of appending the graphs to the same anchor point. Interestingly, if we narrow
the window of our web browser to less that the width of both of our graphs side by side, the
browser will automatically move one of the graphs to a position below the first in much the
same way that text will wrap on a page.
For a very simple mechanism of putting two graphs (or any two d3.js generated images) on a
single page, this will work, but we dont have a lot of control over the positioning.
409
Remembering that the <div> tag defines a division or a section in an HTML document.
Therefore we are labelling specific sections in our web page .
Now all we need to do is to tell each graph to append itself to either of these ID selectors. We do
this by replacing the selected section in our JavaScript code with the appropriate ID selector as
follows;
var chart1 = d3.select("#area1")
.append("svg")
and
var chart2 = d3.select("#area2")
.append("svg")
With these divs added, when you browse to the file, you will find that it looks like this;
410
This looks the same as when the two graphs were wrapping when the browser was narrowed.
However, this time the browser is wide enough to support the two side by side, but they wont
position themselves that way. This is because each div divides the web page. The top graph is in
the div with the ID selector area1 and the bottom graph is in the div with the ID selector area2.
These divs effectively extend for the width of the web page.
The situation that we now find ourselves in is that we have control over where the graphs will
be anchored, but we dont have much flexibility for arranging those anchors. This is where
Bootstrap comes in.
411
The spans can be combined to create larger spaces for larger content. The example below has a
single span6 and two span3s.
The spans will change height dynamically to fit their contents. So if there was a larger item in
the span6 example given above (perhaps a graph), it would expand like so;
The way to set these rows and spans up is by dividing the screen using divs and assigning them
class types that match the grid layout.
For example, to create our example of a single row with a span6 and two span3s we would use
the following html code as our baseline.
412
<div class="row">
<div class="span6"></div>
<div class="span3"></div>
<div class="span3"></div>
</div>
In this example code we can see the row div is enclosing the three spans. We can extend the
comparison by putting the code into our graphic example.
To add content to the structure, all that is needed is to put our web page components between
the <div class="span#"> and </div> tags.
Later we will look (briefly) at more complex configurations that might be useful.
http://bl.ocks.org/d3noob/raw/5987480/
413
<head>
<link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
Then include the lines to load the jquery.js and bootstrap.min.js files just after the line that
loads the d3.js file.
<script src="http://code.jquery.com/jquery.js"></script>
<script src="js/bootstrap.min.js"></script>
What well do to make things simple is to create a Bootstrap layout that is made up of a single
row with just two span6 elements in it. The following code will do this nicely and should go
after the </style> tag and before the <body> tag.
<div class="row">
<div class="span6"></div>
<div class="span6"></div>
</div>
Now we add in our ID selectors in a clever way by incorporating them into the divs that we have
just entered. So remembering the code for our original two selectors
<div id="area1"></div>
<div id="area2"></div>
The last thing we need to do is to change the d3.select from selecting the body of the web page
to selecting our two new ID selectors area1 and area2.
var chart1 = d3.select("#area1")
.append("svg")
and
414
Et viola! Our new web page has two graphs which are settled into their own specific section.
To provide another example of the flexibility of the layout schema, we can take our row / span
layout section and adapt it so that our graphs are in two separate sections with a third, smaller,
section in the middle describing the graphs.
If we start with our previously entered spans with their ID selectors;
<div class="row">
<div class="span6" id="area1"></div>
<div class="span6" id="area2"></div>
</div>
We can change the spans to span5 and add an additional span2 in between with some text
(remember, the total number of spans has to add up to 12).
<div class="row">
<div class="span6" id="area1"></div>
<div class="span2">
To the left is a graph showing the anticipated profits
of the 'Widget Incorporated' company.
On the right is the anticipated cost of production as
the number of Widgets is increased.
Clearly we will be RICH!
</div>
<div class="span6" id="area2"></div>
</div>
415
Neither of these examples is particularly elegant in terms of its layout.I am relying on you to
bring the prettiness!
416
It looks slightly complex with a nesting of spans and rows, and the end result is only 5 separate
sections, but its really not too hard to put together if you start in the right place and build it up
piece by piece.
Well start in the middle and work our way out. The first piece to consider is the two side-by-side
span4s.
Two span4s
A Single span8
417
418
Because this entire block forms part of another (larger) row, we need to enclose it in its own
span8 (since this is part is only span8 wide).
And for the code the new span8 div wraps all the current code we have.
<div class="span8">
<div class="row">
<div class="span4"></div>
<div class="span4"></div>
</div>
<div class="row">
<div class="span8"></div>
</div>
</div>
419
420
The span4 and the complex span8 need to be in their own row
421
Finally we need to place another row with a span12 in it above our current work.
Again, we need to place the row and span before our current code so that it appears above the
current code on the page.
<div class="row">
<div class="span12"></div>
</div>
<div class="row">
<div class="span4"></div>
<div class="span8">
<div class="row">
<div class="span4"></div>
<div class="span4"></div>
</div>
<div class="row">
<div class="span8"></div>
</div>
</div>
</div>
phpMyAdmin
Im not one to dwell on the command line for too long if it can be avoided (sorry). So in this
section youll see me delving into a really neat program for managing your MySQL database
called phpMyAdmin (http://www.phpmyadmin.net/home_page/index.php).
As the name would suggest, its been written in PHP and as we know, thats a sign that were
talking about a web based application. In this case phpMyAdmin is intended to allow a wide
range of administrative operations with MySQL databases via a web browser. You can find a
huge amount of information about it on the web as it is a freely available robust platform that
has been around for well over a decade.
If you have followed my suggestion earlier in the book to install WAMP or you have
phpMyAdmin installed already youre in luck. If not, Im afraid that I wont be able to provide
any guidance on its installation. I just dont have the experience to provide that level of support.
423
Clicking on this icon will provide you with a range of options, including opening phpMyAdmin.
Opening phpMyAdmin
Go ahead and do this and the phpMyAdmin page will open in your browser.
The page youre presented with has a range of tabs, and we want to select the Databases tab.
424
From here we can create ourselves a new database simply by giving it a name and selecting
Create. I will create one called homedb.
Cool, now we get to create a table. Whats a table? Didnt we create our database already?
425
Create a table
Ive chosen data2 as a name since we will put the same data as we have in the data2.tsv file in
there. Thats why there are three columns for the date, close and open columns that we have in
the data2.tsv file.
So, after clicking on the Go button, I get the following screen where I get to enter all the pertinent
details about what I will have in my table.
Im keeping it really simple by setting the date column to be plain text (I make the presumption
that it could be a date format, but as it gets parsed into a date/time value when its ingested
into D3, Im fairly comfortable that we can get away with formatting it as TEXT), and the two
numeric columns to be decimals with 8 digits overall and 2 of those places for the digits to the
right of the decimal point.
The selection of the most efficient data type to maximise space or speed is something
of an obsession (as it sometimes needs to be) where databases are large and need to
have fast access times, but in this case were more concerned with getting a result than
perfection.
426
Once entered, you can scroll down to the bottom of that window and select the Save button.
Cool, now you are presented with your table (click on the table name in the left hand panel) and
the details of it in the main panel.
427
17-Apr-12,543.70,180.34
16-Apr-12,580.13,210.23
13-Apr-12,605.23,223.45
12-Apr-12,622.77,201.56
11-Apr-12,626.20,212.67
10-Apr-12,628.44,310.45
9-Apr-12,636.23,350.45
5-Apr-12,633.68,410.23
4-Apr-12,624.31,430.56
3-Apr-12,629.32,460.34
2-Apr-12,618.63,510.34
30-Mar-12,599.55,534.23
29-Mar-12,609.86,578.23
28-Mar-12,617.62,590.12
27-Mar-12,614.48,560.34
26-Mar-12,606.98,580.12
I know it doesnt look quite as pretty, but csv files are pretty ubiquitous which is why so many
different programs support them as an input and output file type. (To save everyone some time
and trouble I have saved the data.csv file into the D3 Tips and Tricks example files folder (under
data)).
So armed with this file, click on the Import tab in our phpMyAdmin window and choose your
file.
428
The format should be automatically recognised and the format specific options at the bottom of
the window should provide sensible defaults for the input. Lets click on the Go button and give
it a try.
Successful import!
Woo Hoo!
Now if you click on the browse tab, theres your data in your table!
429
Sweet!
The last thing that we should do is add a user to our database so that we dont end up accessing
it as the root user (not too much of a good look).
So select the homedb reference at the top of the window (between localhost and data2).
Then click on the Privileges tab to show all the users who have access to homedb and select
Add a new user
430
Then on the new user create a user, use the Local host and put in an appropriate password.
In this case, the user name is homedbuser and the password is homedbuser (dont tell).
The other thing to do is restrict what this untrusted user can do with the database. In this case
we can fairly comfortably restrict them to SELECT only;
431
Yay!
Believe it or not, thats pretty much it. There were a few steps involved, but theyre hopefully
fairly explanatory and I dont imagine theres anything too confusing that a quick Googling cant
fix.
We actually already have a query operating on our table. Its the bit in the middle that looks like;
432
SELECT *
FROM `data2`
LIMIT 0, 30
This particular query is telling the database homedb (since thats where the query was run from)
to SELECT everything (*) FROM the table data2 and when we return the data, to LIMIT the returned
information to those starting at record 0 and to only show 30 at a time.
You should also be able to see the data in the main body of the window.
So, lets write our own query. We can ask our query in a couple of different ways. Either click on
the SQL tab and you can enter it there, or click on the menu link that says Edit in the current
window. I prefer the Edit link since it opens a separate little window which lets you look at
the returned data and your query at the same time.
So heres our window and in it Ive written the query we want to run.
SELECT `date`, `close` FROM `data2`
You will of course note that I neglected to put anything about the LIMIT information in there.
Thats because it gets added automatically to your query anyway using phpMyAdmin unless
you specify values in your query.
So in this case, our query is going to SELECT all our values of date and close FROM our table
data2.
433
There we go!
If youre running the query as root you may see lots of other editing and copying and deleting
type options. Dont fiddle with them and they wont bite.
Righto Thats the query were going to use. If you look at the returned information with a bit
of a squint, you can imagine that its in the same type of format as the *.tsv or *.csv files. (header
at the top and ordered data underneath).
All that we need to do now is get our MySQL query to output data into d3.js.
Enter php!
434
<?php
$username = "homedbuser";
$password = "homedbuser";
$host = "localhost";
$database="homedb";
$server = mysql_connect($host, $username, $password);
$connection = mysql_select_db($database, $server);
$myquery = "
SELECT `date`, `close` FROM `data2`
";
$query = mysql_query($myquery);
if ( ! $query ) {
echo mysql_error();
die;
}
$data = array();
for ($x = 0; $x < mysql_num_rows($query); $x++) {
$data[] = mysql_fetch_assoc($query);
}
echo json_encode($data);
mysql_close($server);
?>
Its pretty short, but it packs a punch. Lets go through it and see what it does.
The <?php line at the start and the ?> line at the end form the wrappers that allow the requesting
page to recognise the contents as php and to execute the code rather than downloading it for
display.
The following lines set up a range of important variables;
$username = "homedbuser";
$password = "homedbuser";
$host = "localhost";
$database="homedb";
Hopefully you will recognise that these are the configuration details for the MySQL database
that we set up. Theres the user and his password (dont worry, because the script isnt returned
to the browser, the browser doesnt get to see the password and in this case our user has a very
limited set of privileges remember). Theres the host location of our database (in this case its
435
local, but if it was on a remote server, we would just include its address) and theres the database
were going to access.
Then we use those variables to connect to the server
$server = mysql_connect($host, $username, $password);
Then we have our query in a form that we can paste into the right spot and its easy to use.
$myquery = "
SELECT `date`, `close` FROM
";
`data2`
I have it like this so all I need to do to change the query I use is paste it into the middle line there
between the speech-marks and Im done. Its just a convenience thing.
The query is then run against the database with the following command;
$query = mysql_query($myquery);
and then we check to see if it was successful. If it wasnt, we output the MySQL error code;
if ( ! $query ) {
echo mysql_error();
die;
}
Then we declare the $data variable as an array ($data = array();) and feed the returned
information from our query into $data array;
for ($x = 0; $x < mysql_num_rows($query); $x++) {
$data[] = mysql_fetch_assoc($query);
}
(thats a fancy little piece of code that gets the information row by row and puts it into the array)
We then return (echo) the $data array in json format (echo json_encode($data);) into whatever
ran the data2.php script (well come back to this in a minute).
Then finally we close the connection to the server;
436
mysql_close($server);
Whew!
That was a little fast and furious, but I want to revisit the point that we covered in the part about
echoing the data back to whatever had requested it. This is because we are going to use it directly
in our d3.js script, but we can actually run the script directly by opening the file in our browser.
So if you can navigate using your browser to this file and run it (WAMP should be your friend
here again) this is what you should see printed out on your screen;
[{"date":"1-May-12","close":"58.13"},
{"date":"30-Apr-12","close":"53.98"},
{"date":"27-Apr-12","close":"67.00"},
{"date":"26-Apr-12","close":"89.70"},
{"date":"25-Apr-12","close":"99.00"},
{"date":"24-Apr-12","close":"130.28"},
{"date":"23-Apr-12","close":"166.70"},
{"date":"20-Apr-12","close":"234.98"},
{"date":"19-Apr-12","close":"345.44"},
{"date":"18-Apr-12","close":"443.34"},
{"date":"17-Apr-12","close":"543.70"},
{"date":"16-Apr-12","close":"580.13"},
{"date":"13-Apr-12","close":"605.23"},
{"date":"12-Apr-12","close":"622.77"},
{"date":"11-Apr-12","close":"626.20"},
{"date":"10-Apr-12","close":"628.44"},
{"date":"9-Apr-12","close":"636.23"},
{"date":"5-Apr-12","close":"633.68"},
{"date":"4-Apr-12","close":"624.31"},
{"date":"3-Apr-12","close":"629.32"},
{"date":"2-Apr-12","close":"618.63"},
{"date":"30-Mar-12","close":"599.55"},
{"date":"29-Mar-12","close":"609.86"},
{"date":"28-Mar-12","close":"617.62"},
{"date":"27-Mar-12","close":"614.48"},
{"date":"26-Mar-12","close":"606.98"}]
437
We have created a database, populated it with information, worked out how to extract a subset
of that information and how to do it in a format that d3.js understands. Now for the final act!
And you will find it slightly deflating how simple it is.
All we have to do is take our simple-graph.html file and make the following change;
d3.json("php/data2.php", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
Here we have replaced the part of the code that read in the data file as data.tsv with the
equivalent that reads the php/data2.php file in as json (d3,json).
Thats it.
What it does is we tell d3.js to go and get a json file and when it strikes the data2.php file, it
executes the script in the file and returns the encoded json information directly to d3.js. How
cool is that?
And here is the result.
Sure, it looks kind of familiar, but it represents a significant ability for you to return data from a
database and present it on a web page.
438
If we want to generate a standard date / time unit from MySQL we can do it by grouping the
separate parts together using the CONCAT command like so;
SELECT CONCAT(`ORI_YEAR`,'-',`ORI_MONTH`,'-',`ORI_DAY`,',`ORI_HOUR`,
':',`ORI_MINUTE`,':',`ORI_SECOND`)
FROM `nzeq1012`
Notice that as well as grouping the columns we also put in appropriate separators to follow a
good practice. The output looks like so.
2010-7-12 0:15:9.57643
2010-7-12 0:23:45.93486
2010-7-12 0:3:12.54922
This is pretty scruffy looking and certainly doesnt conform to the standard format that were
looking for (YYYY-MM-DD HH:MM:SS). So we can do something a little tricky and tell MySQL that
the value that gets returned is a date / time value and it will automatically format it correctly.
This is as simple as declaring the entire selection as a TIMESTAMP like so;
439
SELECT TIMESTAMP(CONCAT(`ORI_YEAR`,'-',`ORI_MONTH`,'-',`ORI_DAY`,
' ',`ORI_HOUR`,':',`ORI_MINUTE`,':',`ORI_SECOND`))
FROM `nzeq1012`
This is certainly much better, but we have a seconds value that includes a decimal component.
To eliminate the decimal portion we use the ROUND function as follows;
SELECT TIMESTAMP(CONCAT(`ORI_YEAR`,'-',`ORI_MONTH`,'-',`ORI_DAY`,
' ',`ORI_HOUR`,':',`ORI_MINUTE`,':',ROUND(`ORI_SECOND`)))
FROM `nzeq1012`
Neat.
440
Notice that as well as grouping the year, month and day columns we also put in appropriate
separators (dashes(-)) to make it look nice. The output looks like so.
2010-7-2
2010-10-12
2010-3-26
To go one step further you could enclose the entire concatenated grouping in a DATE
command which would format the result in the standard date format YYYY-MM-DD.
SELECT DATE(CONCAT(`ORI_YEAR`, '-', `ORI_MONTH`, '-', `ORI_DAY`))
FROM `nzeq1012`
Which produces
2010-07-02
2010-10-12
2010-03-26
441
This is because long is a word reserved for other uses in MySQL, so using it as a variable is
difficult. However, enclose the long in quotes (as follows) and it will work fine.
SELECT `LAT` AS lat, `LONG` AS 'long'
FROM `nzeq1012`
Rounding numbers
Rounding numbers with a fractional component is a common requirement. The ROUND function
can be used in a couple of ways to round numbers.
Firstly, by stating ROUND(x) (where x is the argument) the function will round a number to only
the integer component. However you can also use ROUND(x,d), where d is the number of decimal
places to round to.
For example, using the following data for earthquakes where mag is the magnitude and depth
is the depth of the quake, we could reasonably want to massage the data so that the magnitude
was represented by a number with a single decimal place and the depth was only the integer.
+------+--------+
|
mag|
depth|
+------+--------+
| 2.555| 56.2691|
| 2.226| 6.2300|
| 2.055| 33.1684|
| 1.411| 12.0000|
| 1.976| 6.3498|
+------+--------+
+----+------+
| mag| depth|
+----+------+
| 2.6|
56|
| 2.2|
6|
| 2.1|
33|
| 1.4|
12|
| 2.0|
6|
+-----------+
442
And if you include a readme file formatted using markdown you can have a nice little explanation
of how your visualization works.
The front rendering page includes any markdown notes and the code (not the full screen) is
optimised to accept visualizations of 960x500 pixels (although you can make them other sizes,
its just that this is an optimum size). Of course there is always the full screen mode to render
your creation in its full glory if necessary.
If I was to pass on any advice when using bl.ocks.org, please consider others who will no doubt
view your work and wonder how you achieved your magic. Help them along where possible
with a few comments in the readme.md file because sharing is caring :-).
http://bl.ocks.org/
444
to this
445
446
Your graph is a thing of rare beauty and the community needs to marvel at your brilliance. Of
course this is a breeze with bl.ocks.org. Once you have all the code sorted out, and all data files
made accessible, bl.ocks.org can display the graph with the code and can even open the graph in
its own window. The person responsible for bl.ocks.org? Mike Bostock of course (wherever does
he get the time?).
Clicking on the bl.ocks.org button on the gist page (load the extension available from the main
page of bl.ocks.org) takes you to see your graph.
Wow! Impressive.
So you think that will make a fine addition to your collection of awesome graphs and if you click
on your GitHub user name that is in the top left of the screen you go to a page that lays out all
your graphs with a thumbnail giving a sneak preview of what the user can expect.
447
448
You can clone the gist to a local folder with the command;
git clone https://gist.github.com/4414436.git
Or if youre using OSX, the following command has been passed on by Alex Hornbake
as an alternative (thanks Alex).
git clone [email protected]:4414436.git
(The url is the one copied from the Clone this gist box.)
This will create a folder with the id (the number) of the gist in your local GitHub working
directory.
https://gist.github.com/441443
449
And now commit it to your gist with the following command in the Git Shell;
git commit -m "Thumbnail image added"
Now we need to push the commit to the remote gist (you may be asked for your GitHub user
name and password if you havent done this before) with the following command;
git push
http://www.d3noob.org/2012/12/loading-thumbnail-into-gist-for.html
450
Push! Push!
OK, now you can go back to the web page for your gist and refresh it and scroll on down
A thumbnail is born
Woo Hoo!
(I know it doesnt look like much, but this is a VERY simple graph :-)).
Now for the real test. Go back to your home page for your blocks on bl.ocks.org and refresh the
page.
Oh yes. You may now bask in the sweet glow of victory. And as a little bit of extra fancy, if you
move your mouse over the image it translates up slightly!
Wrap up.
The steps to get your thumbnail into the gist arent exactly point and click, but the steps you
need to take are fairly easy to follow. As promised, here is the abridged list of steps that will
avoid you going through the several previous pages.
1. Create your public gist on https://gist.github.com/
https://gist.github.com/
451
Appendices
Simple Line Graph
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
Appendices
</script>
</body>
453
Appendices
body {
font: 12px Arial;
}
text.shadow {
stroke: #fff;
stroke-width: 2.5px;
opacity: 0.9;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
line {
stroke: grey;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
.area {
fill: lightsteelblue;
stroke-width: 0;
}
454
Appendices
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")"
);
// function for the x grid lines
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
}
// function for the y grid lines
function make_y_axis() {
return d3.svg.axis()
.scale(y)
455
Appendices
.orient("left")
.ticks(5)
}
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the filled area
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
// Draw the x Grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
// Draw the y Grid lines
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add a the text label white background for legibility
456
Appendices
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", margin.top - (height / 2))
.attr("dy", ".71em")
.style("text-anchor", "end")
.attr("class", "shadow")
.text("Price ($)");
// Add the text label for the Y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", margin.top - (height / 2))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
// Add the title
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Price vs Date Graph");
});
</script>
</body>
457
Appendices
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script>
458
Appendices
459
Appendices
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>
460
Appendices
Bar Chart
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var
parseDate = d3.time.format("%Y-%m").parse;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y-%m"));
var yAxis = d3.svg.axis()
.scale(y)
461
Appendices
.orient("left")
.ticks(10);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
d3.csv("bar-data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
462
Appendices
463
Appendices
Linking Objects
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 449;
var height = 249;
var word = "gongoozler";
var holder = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// draw a rectangle
holder.append("a")
.attr("xlink:href", "http://en.wikipedia.org/wiki/"+word)
.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("height", 100)
.attr("width", 200)
.style("fill", "lightgreen")
.attr("rx", 10)
.attr("ry", 10);
// draw text on the screen
holder.append("text")
.attr("x", 200)
.attr("y", 100)
.style("fill", "black")
.style("font-size", "20px")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("pointer-events", "none")
.text(word);
</script>
464
Appendices
</body>
A live version of this code can be found online on bl.ocks.org and GitHub.
http://bl.ocks.org/d3noob/8150631
https://gist.github.com/d3noob/8150631
465
Appendices
466
Appendices
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<p id="chart">
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/sankey.js"></script>
<script>
467
Appendices
468
Appendices
</script>
</body>
</html>
469
Appendices
470
471
Appendices
Appendices
472
473
Appendices
Appendices
474
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
];
Appendices
475
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + so\
urce.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"\
; });
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end"\
: "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")\
"; });
Appendices
476
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"\
; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + sou\
rce.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
Appendices
477
Appendices
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
path.link.twofive {
opacity: 0.25;
}
path.link.fivezero {
opacity: 0.50;
}
path.link.sevenfive {
opacity: 0.75;
}
path.link.onezerozero {
opacity: 1.0;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
478
Appendices
</style>
<body>
<script>
479
480
Appendices
link.type = "onezerozero";
}
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("click", click)
.on("dblclick", dblclick)
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
Appendices
481
Appendices
.style("fill", "black")
.style("stroke", "none")
.style("font", "10px sans-serif");
}
});
</script>
</body>
</html>
482
Appendices
Bullet Chart
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
padding-top: 40px;
position: relative;
width: 800px;
}
button {
position: absolute;
right: 40px;
top: 10px;
}
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
.bullet
</style>
<button>Update</button>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/bullet.js"></script>
<script>
483
Appendices
484
Appendices
return function(d) {
return Math.max(0, d + k * (Math.random() - .5));
};
}
</script>
</body>
485
Appendices
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/topojson.v0.min.js"></script>
<script>
486
Appendices
.attr("d", path)
// load and display the cities
d3.csv("data/cities.csv", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5)
.style("fill", "red");
});
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
g.selectAll("circle")
.attr("d", path.projection(projection));
});
svg.call(zoom)
</script>
</body>
</html>
487