-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathindex.html
executable file
·1045 lines (904 loc) · 40.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Mapping with D3 - Maptime Boston</title>
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui'>
<link rel='stylesheet' href='css/reveal.css'>
<link rel='stylesheet' href='css/theme/beige.css' id='theme'>
<style>
.reveal .slides .left{ text-align: left; }
</style>
<!-- Code syntax highlighting -->
<link rel='stylesheet' href='lib/css/zenburn.css'>
<!-- Printing and PDF exports -->
<script>
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match(/print-pdf/gi) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName('head')[0].appendChild(link);
</script>
<!--[if lt IE 9]>
<script src='lib/js/html5shiv.js'></script>
<![endif]-->
</head>
<body>
<div class='reveal'>
<!-- Any section element inside of this container is displayed as a slide -->
<div class='slides'>
<section>
<h1>Mapping with D3</h1>
<h3>A friendly introduction</h3>
<p>
<small>Andy Woodruff for Maptime Boston</small><br>
<small><a href='http://twitter.com/maptimeboston' target='_blank'>@maptimeBoston</a> | <a href='http://twitter.com/awoodruff' target='_blank'>@awoodruff</a></small>
</p>
</section>
<section>
<p>Follow along!</p>
<p><a href='http://maptimeboston.github.io/d3-maptime' target='_blank'>maptimeboston.github.io/d3-maptime</a></p>
<p>Or get the code for examples:</p>
<p><a href='http://github.com/maptimeboston/d3-maptime' target='_blank'>github.com/maptimeboston/d3-maptime</a></p>
</section>
<section>
<section>
<h2>Before we begin...</h2>
<p>D3 is <strong>HARD</strong> for beginners. Here at Maptime we'll try to make enough sense of it to get you on your way to making amazing maps, but we strongly recommend spending time with <a href='#/2/1'>more thorough guides and examples.</a></p>
</section>
<section>
<ul>
<li><a href='https://github.com/d3/d3/wiki' target='_blank'>D3 documentation.</a> Start here!</li>
<li>Be sure to note the <a href='https://github.com/d3/d3/wiki/Tutorials' target='_blank'>tutorials page there</a>.</li>
<li><a href='http://alignedleft.com/tutorials/d3' target='_blank'>Scott Murray's D3 tutorials</a> are especially recommended.</li>
<li>Browse <a href='http://bl.ocks.org/mbostock' target='_blank'>Mike Bostock's examples</a>. He is someone who understands the <a href='http://bost.ocks.org/mike/example/' target='_blank'>power of examples</a>.</li>
<li>For examples of specific pieces of D3, try <a href='http://bl.ocksplorer.org/' target='_blank'>bl.ocksplorer.org</a>, by Irene Ros.</li>
</ul>
</section>
</section>
<section>
<section>
<p>If you see the down arrow in the corner of a slide, press down to see links to helpful resources on the subject at hand.</p>
<div style='margin:auto;width:0;height:0;border:12px solid transparent;border-top-color:#8b743d;border-top-width:22px;'></div>
</section>
<section>
<p>Yeah, you get the hang of it!</p>
</section>
</section>
<section>
<h2>What is D3?</h2>
<p>D3 is <a href='http://d3js.org' target='_blank'>Data-Driven Documents</a>:</p>
<blockquote class='left' cite='http://d3js.org'>
“D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.”
</blockquote>
</section>
<section>
<h1>What the heck does that mean?</h1>
</section>
<section>
<h2>It means taking data...</h2>
<table>
<thead>
<tr>
<th>City</th>
<th># of rats</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cambridge</td>
<td>400</td>
<tr>
<tr>
<td>Boston</td>
<td>900</td>
<tr>
<tr>
<td>Somerville</td>
<td>300</td>
<tr>
<tr>
<td>Brookline</td>
<td>600</td>
<tr>
</tbody>
</table>
</section>
<section>
<h2>...binding it to HTML or SVG elements...</h2>
<pre><code>
<rect x='0' width='15' fill='#d1c9b8'></rect>
<rect x='25' width='15' fill='#d1c9b8'></rect>
<rect x='50' width='15' fill='#d1c9b8'></rect>
<rect x='75' width='15' fill='#d1c9b8'></rect>
</code></pre>
</section>
<section>
<h2>...and manipulating those elements based on the data.</h2>
<svg width='200' height='300' style='margin-left:-100px'>
<rect x='105' width='15' height='60' y='90' fill='#d1c9b8'></rect>
<rect x='130' width='15' height='135' y='15' fill='#d1c9b8'></rect>
<rect x='155' width='15' height='45' y='105' fill='#d1c9b8'></rect>
<rect x='180' width='15' height='90' y='60' fill='#d1c9b8'></rect>
<line x1='100' x2='200' y1='150' y2='150' stroke='#666'></line>
<line x1='100' x2='100' y1='150' y2='0' stroke='#666'></line>
<text font-size='12' text-anchor='end' transform='translate(115, 160) rotate(-45)' fill='#666'>Cambridge</text>
<text font-size='12' text-anchor='end' transform='translate(140, 160) rotate(-45)' fill='#666'>Boston</text>
<text font-size='12' text-anchor='end' transform='translate(165, 160) rotate(-45)' fill='#666'>Somerville</text>
<text font-size='12' text-anchor='end' transform='translate(190, 160) rotate(-45)' fill='#666'>Brookline</text>
<text font-size='12' text-anchor='end' transform='translate(90, 40) rotate(-90)' fill='#666'>Number of rats</text>
</svg>
</section>
<section>
<h2>Simple, right?</h2>
<p>There's more to it, of course, but building on that core concept leads to <a href='https://github.com/mbostock/d3/wiki/Gallery' target='_blank'>spectacular things</a>.</p>
</section>
<section>
<p>D3 was created by <a href='http://bost.ocks.org/mike/' target='_blank'>Mike Bostock</a> using his giant brain. It is an <a href='https://github.com/d3/d3' target='_blank'>open-source</a> library with many additional <a href='https://github.com/d3/d3/graphs/contributors' target='_blank'>contributors</a>. (As a mapper, you will especially be awed by <a href='http://www.jasondavies.com/' target='_blank'>Jason Davies</a> and his contributions.)</p>
</section>
<section>
<section>
<h2>D3 is not a graphical representation</h2>
<p>D3 is not a magic tool that draws and styles charts, maps, etc. Rather, it provides a means for <strong>YOU</strong> to create and style web-standard documents based on your data.</p>
<p>It's not about charts, nor maps, nor any particular kind of graphic. It is fundamentally about data and web documents.</p>
</section>
<section>
<p>(That said, there are a handful of cases in which D3 does kind of magically draw something for you based on some parameters. A basic example is an <a href='https://github.com/d3/d3-axis' target='_blank'>axis</a>.)
</section>
</section>
<section>
<p>(But let's stick to maps and charts for now.)</p>
</section>
<section>
<h2>Enough introduction.</h2>
<h2>Let's make something!</h2>
</section>
<section>
<iframe data-src='ratmap/index.html' height='600' width='800' scrolling='no'></iframe>
</section>
<section>
<p>Maybe we should start smaller. How about that bar chart from a few slides back?</p>
<svg width='200' height='300' style='margin-left:-100px'>
<rect x='105' width='15' height='60' y='90' fill='#d1c9b8'></rect>
<rect x='130' width='15' height='135' y='15' fill='#d1c9b8'></rect>
<rect x='155' width='15' height='45' y='105' fill='#d1c9b8'></rect>
<rect x='180' width='15' height='90' y='60' fill='#d1c9b8'></rect>
<line x1='100' x2='200' y1='150' y2='150' stroke='#666'></line>
<line x1='100' x2='100' y1='150' y2='0' stroke='#666'></line>
<text font-size='12' text-anchor='end' transform='translate(115, 160) rotate(-45)' fill='#666'>Cambridge</text>
<text font-size='12' text-anchor='end' transform='translate(140, 160) rotate(-45)' fill='#666'>Boston</text>
<text font-size='12' text-anchor='end' transform='translate(165, 160) rotate(-45)' fill='#666'>Somerville</text>
<text font-size='12' text-anchor='end' transform='translate(190, 160) rotate(-45)' fill='#666'>Brookline</text>
<text font-size='12' text-anchor='end' transform='translate(90, 40) rotate(-90)' fill='#666'>Number of rats</text>
</svg>
</section>
<section>
<p>Lay down some boilerplate HTML and load the D3 library.</p>
<pre><code>
<html>
<head>
<title>A D3 chart</title>
<script src='https://d3js.org/d3.v4.min.js'></script>
</head>
<body>
<script>
// our code will go here
</script>
</body>
</html>
</code></pre>
</section>
<section>
<p>Let's begin with some <strong>data</strong>. We'll use those rat numbers from the earlier table. Inside the <code><script></code> tag, create a simple array of the numbers.</p>
<pre><code>
var ratData = [400, 900, 300, 600];
</code></pre>
</section>
<section>
<p>Now let's make some SVG elements to which the data will be attached.</p>
<pre><code>
<html>
<head>
<title>A D3 chart</title>
<script src='https://d3js.org/d3.v4.min.js'></script>
</head>
<body>
<svg width='100' height='150'>
<rect x='0' width='15' fill='#d1c9b8'></rect>
<rect x='25' width='15' fill='#d1c9b8'></rect>
<rect x='50' width='15' fill='#d1c9b8'></rect>
<rect x='75' width='15' fill='#d1c9b8'></rect>
</svg>
<script>
var ratData = [400, 900, 300, 600];
</script>
</body>
</html>
</code></pre>
</section>
<section>
<p>Back in the <code><script></code>, it's show time.</p>
<pre><code>
var ratData = [400, 900, 300, 600];
d3.selectAll('rect')
.data(ratData)
.attr('height', function(d){
return d/10 * 1.5;
})
.attr('y', function(d){
return 150 - d/10 * 1.5;
});
</code></pre>
</section>
<section>
<h1>What just happened?</h1>
</section>
<section>
<section>
<pre><code>d3.selectAll('rect')</code></pre>
<p>D3 has <code>select()</code> and <code>selectAll()</code> methods to find single or multiple <a href='https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model' target='_blank'>DOM</a> elements, respectively. This works much like <a href='jquery.com' target='_blank'>jQuery</a>.</p>
<pre><code>
d3.selectAll('rect'); // select all SVG rect elements
d3.select('#boston'); // select an element where id='boston'
d3.selectAll('.bar'); // select all elements with the class 'bar'
</code></pre>
<p>Since <code>selectAll('rect')</code> finds multiple elements, everything in the chain following this will be happening to each of those elements.</p>
</section>
<section>
<p>'DOM elements' basically means HTML or SVG entities, like a <code><div></code> or <code><p></code> or <code><circle></code> element. Elements have properties and styles that we'll be controlling via D3 code.</p>
</section>
</section>
<section>
<p><code>d3.select('rect')</code></p>
<pre><code>
<rect x='0' width='15' fill='#d1c9b8'></rect> <!-- I am selected! -->
<rect x='25' width='15' fill='#d1c9b8'></rect>
<rect x='50' width='15' fill='#d1c9b8'></rect>
<rect x='75' width='15' fill='#d1c9b8'></rect>
</code></pre>
<p><code>d3.selectAll('rect')</code></p>
<pre><code>
<rect x='0' width='15' fill='#d1c9b8'></rect> <!-- I am selected! -->
<rect x='25' width='15' fill='#d1c9b8'></rect><!-- I am selected! -->
<rect x='50' width='15' fill='#d1c9b8'></rect><!-- I am selected! -->
<rect x='75' width='15' fill='#d1c9b8'></rect><!-- I am selected! -->
</code></pre>
</section>
<section>
<pre><code>.data(ratData)</code></pre>
<p>The <code>data()</code> method is the very soul of D3. With it, an array of data is bound to page elements.</p>
<p>In your web inspector, see that the data has been directly attached to the <code><rect></code> elements as a <code>__data__</code> property.</p>
<img src='images/element_data.jpg'/>
</section>
<section>
<section>
<p>In the simplest case, array data is joined to elements in order. So our first <code><rect></code> gets a value of 400, the second 900, and so on. But there are powerful, more sophisticated ways of joining data to elements and specifying which elements get which data values.</p>
</section>
<section>
<p>See Mike Bostock's <a href='http://bost.ocks.org/mike/constancy/' target='_blank'>Object Constancy</a> example and explanation of how key functions can be used in data joins.</p>
</section>
</section>
<section>
<pre><code>.attr(...)</code></pre>
<p>Again like jQuery, D3 has methods to get and set element attributes and styles. They can be used to set hard-coded values...</p>
<pre><code>
var el = d3.select('rect');
el.attr('height', 10); // set the rectangle's height attribute to 10
el.style('opacity', 0.5); // set a CSS style
el.attr('height'); // returns 10
el.style('opacity'); // returns .5
</code></pre>
</section>
<section>
<p>...or they can set values based on the element's <strong>data</strong> by passing a function, which is what we did.</p>
<pre><code>
.attr('height', function(d){ // d = the element's data
return d/10 * 1.5; // return value will assigned to the height attribute
})
</code></pre>
<p>For each element, the function will be invoked with <code>d</code> being its data value (in our case, a number like 400). Whatever the function returns is what the 'height' attribute will be set to.</p>
</section>
<section>
<pre><code>
.attr('attribute_name', function(d, i){
return some_value;
})
</code></pre>
<p>This is a more generalized form of setting attributes based on data. <code>d</code> represents the data bound to the element. <code>i</code> represents the element's zero-based index in the selection. (We'll see that in action later on.)</p>
</section>
<section>
<p>So in an expanded example of our first bar...</p>
<pre><code>.attr('height', function(d, i){
// d = 400
// i = 0
return d/10 * 1.5; // 400/10 * 1.5 = 60
})</code></pre>
<p>and the second bar...</p>
<pre><code>.attr('height', function(d, i){
// d = 900
// i = 1
return d/10 * 1.5; // 900/10 * 1.5 = 135
})</code></pre>
</section>
<section>
<pre><code>
.attr('y', function(d){
return 150 - d/10 * 1.5;
});
</code></pre>
<p>Finally, we do a similar thing for the rectangle's <code>y</code> attribute so that it's aligned to the bottom of our 150-pixel high SVG. (Otherwise, the bars would hang down from the top.)</p>
</section>
<section>
<p>Notice that D3 uses handy method chaining. Methods such as <code>data()</code> and <code>attr()</code> return the selection, allowing us to do multiple things in a row without having to reselect the elements.</p>
</section>
<section>
<h2>You made a chart with D3! Hooray!</h2>
<h3><a href='example1/index.html' target='_blank'>#ratchart</a></h3>
</section>
<section>
<h1>Ready to get confused?</h1>
</section>
<section>
<h2>Creating elements</h2>
<p>It's more often the case that you don't have your data-driven elements pre-baked into the page, but rather create them on the fly. Let's give that a try. Start with another empty HTML page.</p>
<pre><code>
<html>
<head>
<title>A D3 chart</title>
<script src='https://d3js.org/d3.v4.min.js'></script>
</head>
<body>
<script>
// our code will go here
</script>
</body>
</html>
</code></pre>
</section>
<section>
<p>Deep breath. Here's code for the same chart. Don't worry, we'll walk through it all!</p>
<pre><code>
var ratData = [400, 900, 300, 600]; // looks familiar!
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 150);
svg.selectAll('rect')
.data(ratData)
.enter()
.append('rect')
.attr('x', function(d, i){
return i * 25;
})
.attr('width', 15)
.attr('fill', '#d1c9b8')
.attr('height', function(d){
return d/10 * 1.5;
})
.attr('y', function(d){
return 150 - d/10 * 1.5;
});
</code></pre>
</section>
<section>
<pre><code>
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 150);
</code></pre>
<p>First we have to create an SVG element into which our bar rectangles will go. This will look somewhat familiar if you've used jQuery. Select the body and append an svg to it. But there are two key differences from jQuery:</p>
</section>
<section>
<p>1. D3's <code>append()</code> takes only the name of the element, not actual markup or content.</p>
<pre><code>
d3.select('body').append('svg')
// versus
$('body').append('<svg>')
</code></pre>
</section>
<section>
<p>2. D3's <code>append()</code> returns the appended element, not the parent. Thus the attributes we set in the next lines will apply to the SVG, not the body (as they would with jQuery).</p>
<pre><code>
var svg = d3.select('body') // returns body selection
.append('svg') // returns svg selection
.attr('width', 100) // returns svg selection
.attr('height', 150); // returns svg selection
</code></pre>
<p>And because the last method in the chain returns the SVG selection, that's what our <code>svg</code> variable is defined as.</p>
</section>
<section>
<pre><code>
svg.selectAll('rect')
.data(ratData)
</code></pre>
<p>Cool, we're selecting all the <code><rect></code> elements in the SVG and binding data to them...</p>
</section>
<section>
<h1>Wait, what!?</h1>
</section>
<section>
<h1>Those rectangles don't exist!</h1>
</section>
<section>
<section>
<p>This part is hard to grasp at first, but don't worry if you don't understand. For now, just know that this is how you write the code.</p>
<p>If there are no <code><rect></code> elements, we get an empty selection, kind of a placeholder for what's to come. Once we bind data to this selection and append some elements, the selection will contain those elements.</p>
</section>
<section>
<p>For what exactly is going on under the hood with empty selections and the following data binding, take a look at this short <a href='https://northlandia.wordpress.com/2014/11/01/d3-selection-mysteries-solved/' target='_blank'>step-by-step overview</a> by Carl Sack.</p>
</section>
</section>
<section>
<p>This is the basic syntax for creating new elements to match a data array.</p>
<pre><code>
svg.selectAll('rect')
.data(ratData)
.enter()
.append('rect')
</code></pre>
<p><code>enter()</code> refers to new incoming data for which there is not yet an existing <code><rect></code>. (We'll come back to that later.) For each incoming data value, we're appending a <code><rect></code> element.</p>
</section>
<section>
<pre><code>
.attr('x', function(d, i){
return i * 25;
})
</code></pre>
<p>We space the bars our horizontally using that second <code>i</code> argument, the index of each bar in the selection. For our four rectangles, <code>i</code> here will be 0, 1, 2, and 3, giving us x positions of 0, 25, 50, and 75.</p>
<p>After this, we set fixed width and color values, and set the heights as we did in the previous example, and <a href='example2/index.html' target='_blank'>we're done!</a></p>
</section>
<section>
<h2>enter, exit, and update</h2>
</section>
<section>
<p>Now that we have rectangles on the page, we could go back and update them the same way we did in the first example. Maybe we want to change the numbers:</p>
<pre><code>
var newData = [800, 200, 400, 500];
svg.selectAll('rect')
.data(newData)
.attr('height', function(d){
return d/10 * 1.5;
})
.attr('y', function(d){
return 150 - d/10 * 1.5;
});
</code></pre>
<p>Easy. Four new numbers for four bars.</p>
</section>
<section>
<section>
<p>But what if we send it five numbers? Or only three?</p>
<p>This is where <em>enter</em> and <em>exit</em> selections come in. They deal with new elements and unused elements, respectively, based on incoming data. The <em>update</em> selection is what we just dealt with: basically, existing elements.</p>
</section>
<section>
Mike Bostock has a nice example of what we're about to do, showing how the three types of selections can work together: <a href='http://bost.ocks.org/mike/circles/' target='_blank'>Three Little Circles</a>. Also see his <a href='http://bost.ocks.org/mike/selection/' target='_blank'>longer explanation of selections</a> as you progress toward understanding everything.</p>
</section>
</section>
<section>
<h2>enter</h2>
<p>We have four existing bars. Let's say we send our chart a new data array of five numbers.</p>
<pre><code>
var newData = [800, 200, 400, 500, 100];
var selection = svg.selectAll('rect')
.data(newData)
</code></pre>
<p>The <em>enter</em> selection...</p>
<pre><code>
selection.enter()
</code></pre>
<p>...contains one placeholder for that new fifth number, to which we can append a <code><rect></code>. We've already seen the <em>enter</em> selection in action, of course, back at the beginning.</p>
</section>
<section>
<p>So we bind new data and append another rectangle:</p>
<pre><code>var newData = [800, 200, 400, 500, 100];
var selection = svg.selectAll('rect')
.data(newData);
// selection has four rect elements and five data points
// this part will only happen for the new fifth bar
selection.enter()
.append('rect')
.attr('x', function(d, i){
return i * 25;
})
.attr('width', 15)
.attr('fill', '#d1c9b8');</code></pre>
<p>Next we want to set the height of all rectangles. If we do that on the <code>enter()</code> selection above, it will only apply to the one new rectangle. But if we do it on <code>selection</code>, it will only apply to the four previously-existing rectangles.</p>
</section>
<section>
<p>To get everything back together and do work on all elements, we need to <a href="https://github.com/d3/d3-selection/blob/master/README.md#selection_merge" target="_blank">merge</a> the enter and update selections:</p>
<pre><code>
var selection = svg.selectAll('rect') // the UPDATE selection
.data(newData);
selection.enter() // the ENTER selection
.append('rect')
.attr('x', function(d, i){
return i * 25;
})
.attr('width', 15)
.attr('fill', '#d1c9b8')
.merge(selection) // ENTER + UPDATE selections
// everything below now happens to all five bars
.attr('height', function(d){
return d/10 * 1.5;
})
.attr('y', function(d){
return 150 - d/10 * 1.5;
});</code></pre>
<p>This may appear confusing (merging the selection into itself? what?), but follow this pattern to add new elements, then update all elements both old and new.</p>
</section>
<section>
<h2>exit</h2>
<p>Now we have five bars. What if we plug only three numbers into the chart? Well...<p>
<pre><code>
var evenNewerData = [600, 300, 100];
var selection = svg.selectAll('rect')
.data(evenNewerData);
selection
.attr('height', function(d){
return d/10 * 1.5;
})
.attr('y', function(d){
return 150 - d/10 * 1.5;
});
</code></pre>
<p>Okay, so we updated bar heights for those three numbers. But we still have two extra bars left over!</p>
</section>
<section>
<p>The <em>exit</em> selection...</p>
<pre><code>
selection.exit()
</code></pre>
<p>...contains those elements that no longer have data after the join. In this case, it's those last two bars, because we didn't provide any numbers for them. And since we don't need them anymore...</p>
<pre><code>
selection.exit()
.remove();
</code></pre>
<p>Poof! They're gone!</p>
</section>
<section>
<p>If we put everything together, our chart is pretty flexible and can be updated as needed. Here's how that might look.</p>
<pre><code>
var ratData = [400, 900, 300, 600];
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 150);
function drawChart(dataArray){
// create a selection and bind data
var selection = svg.selectAll('rect')
.data(dataArray);
// create new elements wherever needed
selection.enter()
.append('rect')
.attr('x', function(d, i){
return i * 25;
})
.attr('width', 15)
.attr('fill', '#d1c9b8');
.merge(selection) // merge new elements with existing ones, so everything below applies to all
.attr('height', function(d){
return d/10 * 1.5;
})
.attr('y', function(d){
return 150 - d/10 * 1.5;
});
// remove any unused bars
selection.exit()
.remove();
}
drawChart(ratData);
// Now try opening up the console and calling drawChart() with different data arrays.
// The chart will update with the correct number and size of bars.
// drawChart([200, 300, 400, 500, 600, 700])
// drawChart([800, 700, 600])
// and so on
</code></pre>
</section>
<section>
<h1>Phew!</h1>
</section>
<section>
<p>If you're confused, don't worry! Take your time with some of the good tutorials out there. Once you get the hang of selections and data joins, you are well on your way to D3 mastery.</p>
<p>Check out things like Mike Bostock's <a href='https://bl.ocks.org/mbostock/3808218' target='_blank'>update pattern example</a> and his <a href='http://bost.ocks.org/mike/selection/' target='_blank'>explanation of selections</a> to help you get there.</p>
</section>
<section>
<p>So we made a chart. Fine.</p>
<h2>But this isn't Charttime</h2>
</section>
<section>
<h1>It's Maptime!</h1>
</section>
<section>
<h2>Do not be afraid</h2>
<p>Maybe 'D3 is not a graphical representaion' is making sense by now. We had to get pretty specific in setting visual attributes just to make a stupid bar chart. And a map is way more complicated than that, right?</p>
</section>
<section>
<h2>D3 is good at maps</h2>
<p>Don't sweat; it's easier than you might think! Drawing a basic map doesn't take any more code than drawing those four bars.</p>
</section>
<section>
<iframe data-src='example3/index.html' height='600' width='700' scrolling='no'></iframe>
</section>
<section>
<p>All the code that turned GeoJSON data into that map:</p>
<pre><code>
<html>
<head>
<title>A D3 map</title>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='neighborhoods.js'></script>
</head>
<body>
<script>
var width = 700,
height = 580;
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var g = svg.append('g');
var albersProjection = d3.geoAlbers()
.scale(190000)
.rotate([71.057, 0])
.center([0, 42.313])
.translate([width/2, height/2]);
var geoPath = d3.geoPath()
.projection(albersProjection);
g.selectAll('path')
.data(neighborhoods_json.features)
.enter()
.append('path')
.attr('fill', '#ccc')
.attr('d', geoPath);
</script>
</body>
</html>
</code></pre>
</section>
<section>
<h2>D3 and GeoJSON</h2>
<p>In short, D3 has some internal magic that can turn GeoJSON data into screen coordinates. This is not unlike other libraries such as Leaflet, but the result is much more open-ended, not constrained to shapes on a tiled Mercator map.</p>
</section>
<section>
<h2>Time to get mappin'</h2>
<p>Let's walk through that Boston map. First, include the neighborhoods GeoJSON data. We've assigned it to a <code>neighborhoods_json</code> variable in <a href='example3/neighborhoods.js' target='_blank'>neighborhoods.js</a> which is loaded in the document <code><head></code>.</p>
<p>The first bit of JS code should look straightforward now:</p>
<pre><code>
var width = 700,
height = 580;
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var neighborhoods = svg.append('g');
</code></pre>
</section>
<section>
<pre><code>
var albersProjection = d3.geoAlbers()
.scale(190000)
.rotate([71.057, 0])
.center([0, 42.313])
.translate([width/2, height/2]);
</code></pre>
<p>Whoa, does that say <em>projection</em>? If you hear a cartographer get excited about D3, this is why.</p>
<p>D3 supports the <a href='http://www.jasondavies.com/maps/transition/' target='_blank'>map projections you've always dreamed of</a>.</p>
</section>
<section>
<pre><code>
var albersProjection = d3.geoAlbers()
</code></pre>
<p>Back to the code. Above is what a basic projection looks like. It creates a function into which you can plug longitude and latitude values and get projected coordinates back. For example, having created the <code>projection</code> variable, the following call would return some coordinates:</p>
<pre><code>
albersProjection([-71.057, 42.313]); // longitude and latitude of Boston-ish
</code></pre>
</section>
<section>
<p>D3 has a handful of <a href='https://github.com/d3/d3-geo/blob/master/README.md#projections' target='_blank'>projections built in</a>, but there are tons more supported in an <a href='https://github.com/d3/d3-geo-projection/'>external plugin</a>.</p>
</section>
<section>
<pre><code>
var albersProjection = d3.geoAlbers()
.scale(190000)
.rotate([71.057, 0])
.center([0, 42.313])
.translate([width/2, height/2]);
</code></pre>
<p>Back to this.</p>
<ul>
<li><code>scale</code>: honestly, just fiddle with this until it works</li>
<li><code>rotate</code> and <code>center</code>: depending on the projection, you may need to use both of these to center the projection on your area of interest.</li>
<li><code>translate</code>: a pixel offset, commonly specified to ensure that the center of the projection is in the center of the viewing area</li>
</ul>
</section>
<section>
<h2>Geo paths</h2>
<p>Having said a minute ago that you can plug longitude/latitude pairs into a projection, it's rare that you will actually explicitly do this. Instead, it tends to happen behind the scenes with <a href='https://github.com/d3/d3-geo/blob/master/README.md#paths' target='_blank'>path generators</a>.</p>
</section>
<section>
<pre><code>
var geoPath = d3.geoPath()
.projection(albersProjection);
</code></pre>
<p>Once again we are creating a function here. A geo path is a function that takes a GeoJSON feature and returns <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths" target="_blank">SVG path</a> data, based on the specified projection. Put differently in a kind of pseudo-code, we're doing this:</p>
<pre><code>
var geoPath = function(feature){
// grab lat/lon coordinates from geojson feature
// do some crazy magic to turn them into screen coordinates
// return SVG path string
}
</code></pre>
<p>In a moment, it will help to remember that a geo path is a function.</p>
</section>
<section>
<pre><code>
neighborhoods.selectAll('path')
.data(neighborhoods_json.features)
.enter()
.append('path')
.attr('fill', '#ccc')
.attr('d', geoPath);
</code></pre>
<p>Most of this should look familiar by now. Select a bunch of non-existent things, bind data, append new elements, and apply some attributes. Our elements here are SVG paths, which are basically free-form shapes.</p>
</section>
<section>
<p>This part can be a little confusing, though:</p>
<pre><code>
.attr('d', geoPath)
</code></pre>
<p>First, in SVG Land <code>d</code> is an attribute that defines the coordinates of a path. It's a crazy combination of letters and numbers.</p>
<p>Second, this is where it helps to remember that a D3 path is actually a function. The code above is equivalent to a more familiar syntax:</p>
<pre><code>
.attr('d', function(d){
// do magic and return SVG path string
})
</code></pre>
</section>
<section>
<h2>Lather, rinse, repeat</h2>
<p>Again, if you don't really get it, don't worry! You can copy this <a href='example3/index.html' target='_blank'>basic template</a> and re-use it for whatever you're mapping. The only thing you need to do is supply your own GeoJSON and tweak the projection to fit your geography. The more you play around, the more you'll understand.</p>
</section>
<section>
<h2>It's time.</h2>
<h1 class='fragment'><a href='example4/index.html' target='_blank'>#ratmap</a></h1>
</section>
<section>
<iframe data-src='example4/index.html' height='600' width='700' scrolling='no'></iframe>
</section>
<section>
<p>Guess what? Adding a point layer requires almost no extra effort.</p>
<pre><code>
<script src='rodents.js'></script>
</code></pre>
<pre><code>
var rodents = svg.append('g');
rodents.selectAll('path')
.data(rodents_json.features)
.enter()
.append('path')
.attr('fill', '#900')
.attr('stroke', '#999')
.attr('d', geoPath);
</code></pre>
</section>
<section>
<p>All we did is include <a href='examples/rodents.js' target='_blank'>new GeoJSON data (rodents)</a> and then mostly repeat what we did for the neighborhood polygons.</p>
<p>When a path generator encounters point features, it draws a circle. Optionally you can specify a <a href='https://github.com/d3/d3-geo/blob/master/README.md#path_pointRadius' target='_blank'><code>pointRadius</code></a> to control the circle size.</p>
</section>
<section>
<h1>Epilogue</h1>
</section>
<section>
<p>All we did is make a couple of static graphics, but if you're like me, you're exhausted.</p>
<p>Still, there are a few additional things worth mentioning.</p>
</section>
<section>
<h2>CSS styles</h2>
<p>All of the examples herein specified things like color as an attribute on each feature for clarity. But a cleaner way to do it is with CSS. So on the bar chart, for example, instead of</p>
<pre><code>
.attr('fill', '#d1c9b8')
</code></pre>
<p>we could omit that line and include a stylesheet with</p>
<pre><code>
path{ fill: #d1c9b8; }
</code></pre>
</section>
<section>
<h2>Interactivity</h2>
<p>We'll save this for another day, but adding interaction is an obvious next step toward awesome maps.</p>
<p><a href='example5/index.html' target='_blank'>For example...</a></p>
</section>
<section>
<o>Click the rats to get rid of them!</p>
<iframe data-src='example5/index.html' height='600' width='700' scrolling='no'></iframe>
</section>
<section>
<pre><code>
rodents.selectAll('path')
.data(rodents_json.features)
.enter()
.append('path')
.attr('d', geoPath)
.on('click', function(){
d3.select(this).remove();
});
</code></pre>
<p>D3 uses pretty basic event listeners through the <a href='https://github.com/d3/d3-selection/blob/master/README.md#selection_on' target='_blank'></code>on()</code> method</a>.</p>
</section>
<section>
<h2>Transitions</h2>
<p>D3 makes transitions (i.e., animation) easy, which is useful for anything from design flair to time-series graphics.</p>
</section>
<section>
<o>Click the rats to get rid of them!</p>
<iframe data-src='example6/index.html' height='600' width='700' scrolling='no'></iframe>
</section>
<section>
<pre><code>
d3.select(this)
.attr('opacity', 1)
.transition()
.duration(1000)
.attr('x', width * Math.round(Math.random()))
.attr('y', height * Math.round(Math.random()))
.attr('opacity', 0)
.on('end', function(){
d3.select(this).remove();
})
</code></pre>
<p>In short, stick a <code>transition()</code> in there, and anything after that will be animated rather than taking effect immediately. <a href='https://github.com/d3/d3-transition/blob/master/README.md' target='_blank'>Read more about it.</a>
</section>
<section>
<h2>Loading external data</h2>
<p>Although our examples used pre-loaded data, D3 has methods for loading data asynchronously. Check out <a href='https://github.com/d3/d3-request/blob/master/README.md#json' target='_blank'>d3.json()</a> and <a href='https://github.com/d3/d3-request/blob/master/README.md#tsv' target='_blank'>d3.csv()</a> in particular.
</section>
<section>
<h2>Suggested challenge #1</h2>
<p>Each feature in the neighborhoods GeoJSON has a 'density' property. We've learned that attributes can be set based on data. Try making a choropleth map by using the density data to set the 'fill' attribute of the neighborhood polygons.</p>
</section>
<section>
<h2>Suggested challenge #2</h2>
<p>Instead of using the path generator to show points, we could use SVG <code><circle></code> or other elements. (The <a href='https://github.com/maptimeBoston/d3-maptime/blob/gh-pages/example6/index.html#L49-L61' target='_blank'>transition example</a> used images.) Try making a proportional symbol map from data such as <a href='ratcount/ratcount.geojson' target='_blank'>this</a>. Use the map projection to position circles with the 'cx' and 'cy' attributes, and use the numerical data to set the circle size with the 'r' attribute.</p>
</section>
<section>
<h2>Also</h2>
<ul>
<li>Mike Foster's <a href="http://duspviz.mit.edu/d3-workshop/" target="_blank">D3 workshop</a> for DUSPViz, featuring the #ratmap and a lot more depth</li>