Skip to content

Instantly share code, notes, and snippets.

@dpopowich
Last active May 31, 2016 16:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dpopowich/13d3341d492a201797d5204736e26a4d to your computer and use it in GitHub Desktop.
Save dpopowich/13d3341d492a201797d5204736e26a4d to your computer and use it in GitHub Desktop.
Interactive SVG Charts With D3 - Part 2

This single page app is a tutorial demonstrating user interaction with a SVG chart using D3. It is part of a blog post written for Art & Logic, Inc.

Of particular note, if viewing this on bl.ocks.org, it will not format well in the default iframe view...it is designed to be viewed raw, in a modern browser.

/*jslint browser: true, indent: 3, white: true */
"use strict";
/* Weekly closings of the Dow-Jones industrial average, July 1971 - August 1974
*
* array of tuples: (timestamp (milliseconds since epoch), value (dollars))
* From July, 1971 (W27) to August, 1974 (W31). It is sorted on the x-axis!
*
* source: https://datamarket.com/data/list/?q=provider:tsdl
*/
var DOWJONES_WEEKLY = [[47951999000.0, 890.19], [48556799000.0, 901.8], [49161599000.0, 888.51], [49766399000.0, 887.78], [50371199000.0, 858.43], [50975999000.0, 850.61], [51580799000.0, 856.02], [52185599000.0, 880.91], [52790399000.0, 908.15], [53395199000.0, 912.75], [53999999000.0, 911], [54604799000.0, 908.22], [55209599000.0, 889.31], [55814399000.0, 893.98], [56419199000.0, 893.91], [57023999000.0, 874.85], [57628799000.0, 852.37], [58233599000.0, 839], [58838399000.0, 840.39], [59443199000.0, 812.94], [60047999000.0, 810.67], [60652799000.0, 816.55], [61257599000.0, 859.59], [61862399000.0, 856.75], [62467199000.0, 873.8], [63071999000.0, 881.17], [63676799000.0, 890.2], [64281599000.0, 910.37], [64886399000.0, 906.68], [65491199000.0, 907.44], [66095999000.0, 906.38], [66700799000.0, 906.68], [67305599000.0, 917.59], [67910399000.0, 917.52], [68515199000.0, 922.79], [69119999000.0, 942.43], [69724799000.0, 939.87], [70329599000.0, 942.88], [70934399000.0, 942.28], [71539199000.0, 940.7], [72143999000.0, 962.6], [72748799000.0, 967.72], [73353599000.0, 963.8], [73958399000.0, 954.17], [74563199000.0, 941.23], [75167999000.0, 941.83], [75772799000.0, 961.54], [76377599000.0, 971.25], [76982399000.0, 961.39], [77587199000.0, 934.45], [78191999000.0, 945.06], [78796799000.0, 944.69], [79401599000.0, 929.03], [80006399000.0, 938.06], [80611199000.0, 922.26], [81215999000.0, 920.45], [81820799000.0, 926.7], [82425599000.0, 951.76], [83030399000.0, 964.18], [83635199000.0, 965.83], [84239999000.0, 959.36], [84844799000.0, 970.05], [85449599000.0, 961.24], [86054399000.0, 947.23], [86659199000.0, 943.03], [87263999000.0, 953.27], [87868799000.0, 945.36], [88473599000.0, 930.46], [89078399000.0, 942.81], [89683199000.0, 946.42], [90287999000.0, 984.12], [90892799000.0, 995.26], [91497599000.0, 1005.57], [92102399000.0, 1025.21], [92707199000.0, 1023.43], [93311999000.0, 1033.19], [93916799000.0, 1027.24], [94521599000.0, 1004.21], [95126399000.0, 1020.02], [95731199000.0, 1047.49], [96335999000.0, 1039.36], [96940799000.0, 1026.19], [97545599000.0, 1003.54], [98150399000.0, 980.81], [98755199000.0, 979.46], [99359999000.0, 979.23], [99964799000.0, 959.89], [100569599000.0, 961.32], [101174399000.0, 972.23], [101779199000.0, 963.05], [102383999000.0, 922.71], [102988799000.0, 951.01], [103593599000.0, 931.07], [104198399000.0, 959.36], [104803199000.0, 963.2], [105407999000.0, 922.19], [106012799000.0, 953.87], [106617599000.0, 927.89], [107222399000.0, 895.17], [107827199000.0, 930.84], [108431999000.0, 893.96], [109036799000.0, 920], [109641599000.0, 888.55], [110246399000.0, 879.82], [110851199000.0, 891.71], [111455999000.0, 870.11], [112060799000.0, 885.99], [112665599000.0, 910.9], [113270399000.0, 936.71], [113875199000.0, 908.87], [114479999000.0, 852.38], [115084799000.0, 871.84], [115689599000.0, 863.49], [116294399000.0, 887.57], [116899199000.0, 898.63], [117503999000.0, 886.36], [118108799000.0, 927.9], [118713599000.0, 947.1], [119318399000.0, 971.25], [119923199000.0, 978.63], [120527999000.0, 963.73], [121132799000.0, 987.06], [121737599000.0, 935.28], [122342399000.0, 908.42], [122947199000.0, 891.33], [123551999000.0, 854], [124156799000.0, 822.25], [124761599000.0, 838.05], [125366399000.0, 815.65], [125971199000.0, 818.73], [127180799000.0, 848.02], [127785599000.0, 880.23], [128390399000.0, 841.48], [128995199000.0, 855.47], [129599999000.0, 859.39], [130204799000.0, 843.94], [130809599000.0, 820.4], [131414399000.0, 820.32], [132019199000.0, 855.99], [132623999000.0, 851.92], [133228799000.0, 878.05], [133833599000.0, 887.83], [134438399000.0, 878.13], [135043199000.0, 846.68], [135647999000.0, 847.54], [136252799000.0, 844.81], [136857599000.0, 859.9], [137462399000.0, 834.64], [138067199000.0, 845.9], [138671999000.0, 850.44], [139276799000.0, 818.84], [139881599000.0, 816.65], [140486399000.0, 802.17], [141091199000.0, 853.72], [141695999000.0, 843.09], [142300799000.0, 815.39], [142905599000.0, 802.41], [143510399000.0, 791.77], [144115199000.0, 787.23], [144719999000.0, 787.94], [145324799000.0, 784.57], [145929599000.0, 752.58]];
function Chart(svg, width, height, data) {
var self = this;
this.svg = svg;
this.PADDING = 50;
this.WIDTH = width;
this.HEIGHT = height;
this.data = data;
// the formatting function for the x-axis. See
// https://github.com/mbostock/d3/wiki/Time-Formatting#format_multi
this.xaxisFormatter = d3.time.format.utc.multi([
[".%L", function(d) { return d.getUTCMilliseconds(); }],
[":%S", function(d) { return d.getUTCSeconds(); }],
["%I:%M", function(d) { return d.getUTCMinutes(); }],
["%I %p", function(d) { return d.getUTCHours(); }],
//["%a %d", function(d) { return d.getUTCDay() && d.getUTCDate() !== 1; }],
["%m/%d", function(d) { return d.getUTCDate() !== 1; }],
["%b", function(d) { return d.getUTCMonth(); }],
["%Y", function() { return true; }]
]);
// min/max time (X-axis). The data is in sorted order, so we just
// grab the X value from the first and last elements
this.min_time = new Date(data[0][0]);
this.max_time = new Date(data[data.length-1][0]);
// calculate the min/max values
this.min_value = Infinity;
this.max_value = -Infinity;
data.map(function(xy) {
var y = xy[1];
if (y < self.min_value) {
self.min_value = y;
}
if (y > self.max_value) {
self.max_value = y;
}
});
// The domains for each axis
this.xdomain = [this.min_time, this.max_time];
this.ydomain = [this.min_value, this.max_value];
// x-scale, a UTC time scale
this.xscale = d3.time.scale.utc()
.domain(this.xdomain)
.range([0, this.WIDTH-this.PADDING]);
// y-scale
this.yscale = d3.scale.linear()
.domain(this.ydomain)
.range([this.HEIGHT-this.PADDING, 0]);
// initialize d3-tip
this._InitializeTip()
}
Chart.prototype = {
_InitializeTip: function() {
// d3-tip initialization
var self = this,
dateFormatter = d3.time.format("%Y-%m-%d");
this.tip = d3.tip()
.attr('class', 'dot-tip')
.offset(function(d) {
var x = self.xscale(d[0]),
y = self.yscale(d[1]),
left = (x < 100) ? 100 : 0,
top = (y < 100) ? 20 : -10;
return [top, left];
})
.direction(function(d) {
return (self.yscale(d[1]) < 100) ? 's' : 'n';
})
.html(function(d) {
var strDate = "<strong>Date:</strong> " + dateFormatter(new Date(d[0])) + "</br></br>",
strValue = "<strong>Value:</strong> " + d[1] + "</br></br>";
return strDate + strValue;
});
},
_Zoom: function(g, chart) {
/* _Zoom(g, chart)
*
* create the d3 zoom behaviour and event callbacks
*
* g - the element to call the zoom function on
* chart - the Chart instance
*
*/
// a flag to turn off zooming
if (chart.zoomOff) {
return;
}
// create zoom for mouse events on main chart (i.e, mousewheel
// events will zoom in/out) and register event handlers
var zoom = d3.behavior.zoom()
// set the xscale for zooming in the X-coord
.x(chart.xscale)
// on zoom event, call the zoomed function
.on('zoom', zoomed);
// apply it to the passed in element
zoom(g);
function zoomed() {
/* manage zoom event */
var domain = chart.xscale.domain(), // the current domain
delta = domain[1] - domain[0]; // time delta in domain
// if before beginning of data, adjust to beginning with same
// time delta
if (domain[0] < chart.min_time) {
domain[0] = chart.min_time;
domain[1] = Math.min(+chart.min_time + delta, chart.max_time);
}
// if after end of data, adjust to end with same
// time delta
if (domain[1] > chart.max_time) {
domain[0] = Math.max(chart.min_time, +chart.max_time - delta);
domain[1] = chart.max_time;
}
// update the chart xscale domain
chart.xscale.domain(domain);
// when updating xscale, must also update zoom
zoom.x(chart.xscale);
// with new domain set, re-render
chart.Render();
}
},
_Clear: function() {
/* _Clear() */
// select all elements **inside** the SVG and remove them
this.svg.selectAll('*').remove();
},
_DrawLine: function() {
/* DrawLine() */
// reference to `this` used in closures
var self = this;
// create line function for use with PATH d attribute
var line = d3.svg.line()
.x(function(d) {
return self.xscale(d[0]);
})
.y(function(d) {
return self.yscale(d[1]);
});
// create a group to hold the path
var g = this.svg.append('g')
.data([this.data])
.call(this._Zoom, this);
// create a rectangle to collect the mouse events
g.append('rect')
.attr('width', this.WIDTH)
.attr('height', this.HEIGHT)
.style('fill', 'none')
.style('pointer-events', 'all');
// create the PATH element, using the line function above for
// the d attribute. Note the clip-path.
g.append('path')
.attr({fill: 'none',
stroke: 'blue',
d: line,
'clip-path': 'url(#clip)'});
// the clip-path for the PATH, so when we zoom and the line
// extends into the "margins" those portions of the line are
// clipped.
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", this.WIDTH-this.PADDING)
.attr("height", this.HEIGHT-this.PADDING);
return g;
},
_DrawXAxis: function() {
/* _DrawXAxis() */
// append G to hold x-axis
this.svg.append('g')
// give it a class attribute
.classed('axis', true)
// transform it, so it is placed within the padding at the bottom of the chart
.attr('transform', 'translate(0, ' + (this.HEIGHT - this.PADDING) + ')')
// use d3 axis function to generate the x-axis
.call(d3.svg.axis()
// uses the same scaling function as the PATH
.scale(this.xscale)
// see above for details about the formatter
.tickFormat(this.xaxisFormatter)
// orient for the bottom
.orient('bottom'));
},
_DrawYAxis: function() {
/* _DrawYAxis() */
// append G to hold y-axis
this.svg.append('g')
// give it a class attribute
.classed('axis', true)
// transform it, so it is placed within the padding at the right of the chart
.attr('transform', 'translate(' + (this.WIDTH - this.PADDING) + ')')
// use d3 axis function to generate the y-axis
.call(d3.svg.axis()
// uses the same scaling function as the PATH
.scale(this.yscale)
// orient for the right-hand side
.orient('right'));
},
_TipDotsOnly: function(g) {
/* _Tip() -- dots ONLY */
var self = this,
update = g.selectAll('circle').data(g.data()[0])
// append a circle for each point
update.enter()
.append('circle')
.attr('r', 5)
.attr('cx', function(d) {
return self.xscale(d[0]);
})
.attr('cy', function(d) {
return self.yscale(d[1]);
})
.attr('fill', 'blue')
.attr('opacity', 0)
.on('mouseenter', function(d) {
d3.select(this).attr('opacity', .5);
// self.tip.show(d);
})
.on('mouseout', function(d) {
d3.select(this).attr('opacity', 0);
// self.tip.hide(d);
});
// g.call(this.tip);
},
_Tip: function(g) {
/* _Tip() */
var self = this,
update = g.selectAll('circle').data(g.data()[0])
// append a circle for each point
update.enter()
.append('circle')
.attr('r', 5)
.attr('cx', function(d) {
return self.xscale(d[0]);
})
.attr('cy', function(d) {
return self.yscale(d[1]);
})
.attr('fill', 'blue')
.attr('opacity', 0)
.on('mouseenter', function(d) {
d3.select(this).attr('opacity', .5);
self.tip.show(d);
})
.on('mouseout', function(d) {
d3.select(this).attr('opacity', 0);
self.tip.hide(d);
});
g.call(this.tip);
},
OldRender: function() {
/* Part 1 Render() */
// clear the svg canvas
this._Clear();
// draw X-axis
this._DrawXAxis();
// draw Y-axis
this._DrawYAxis();
// draw the line
this._DrawLine();
},
Render: function() {
/* Render() */
// clear the svg canvas
this._Clear();
// draw X-axis
this._DrawXAxis();
// draw Y-axis
this._DrawYAxis();
// draw the line
this._Tip(this._DrawLine());
}
};
function demo_functions(d3, WIDTH, HEIGHT, el_widgets,
disable_run_again, enable_run_again) {
/* Each slide declares a function with a data-function
* attribute. The string value must match a property in
* this object. When user navigates to a slide, the
* associated function is executed.
*/
var __chart = new Chart(d3.select('svg'), WIDTH, HEIGHT, DOWJONES_WEEKLY);
return {
intro: function() {
/* Introduction */
// assign a D3 selection to the single SVG element on the
// page to a variable named svg.
var svg = d3.select('svg');
// create a new chart. WIDTH and HEIGHT are constants
// defined within scope. DOWJONES_WEEKLY is an array of
// two-element arrays, [timestamp, value], e.g:
//
// [[47951999000.0, 890.19], [48556799000.0, 901.8], ...]
var chart = new Chart(svg, WIDTH, HEIGHT, DOWJONES_WEEKLY);
// render the chart
chart.Render();
},
_render: __chart.Render,
_notooltip: __chart.OldRender,
_tip: __chart._Tip,
_tipdotsonly: __chart._TipDotsOnly,
_inittip: __chart._InitializeTip,
notooltip: function() {
var svg = d3.select('svg');
var chart = new Chart(svg, WIDTH, HEIGHT, DOWJONES_WEEKLY);
// swap out render functions
chart.Render = chart.OldRender;
chart.Render();
},
tipdotsonly: function() {
var svg = d3.select('svg');
var chart = new Chart(svg, WIDTH, HEIGHT, DOWJONES_WEEKLY);
// swap out _Tip functions
chart._Tip = chart._TipDotsOnly;
chart.Render();
},
}; // end of return {...};
}
week closing
1971-W27 890.19
1971-W28 901.80
1971-W29 888.51
1971-W30 887.78
1971-W31 858.43
1971-W32 850.61
1971-W33 856.02
1971-W34 880.91
1971-W35 908.15
1971-W36 912.75
1971-W37 911.00
1971-W38 908.22
1971-W39 889.31
1971-W40 893.98
1971-W41 893.91
1971-W42 874.85
1971-W43 852.37
1971-W44 839.00
1971-W45 840.39
1971-W46 812.94
1971-W47 810.67
1971-W48 816.55
1971-W49 859.59
1971-W50 856.75
1971-W51 873.80
1971-W52 881.17
1972-W01 890.20
1972-W02 910.37
1972-W03 906.68
1972-W04 907.44
1972-W05 906.38
1972-W06 906.68
1972-W07 917.59
1972-W08 917.52
1972-W09 922.79
1972-W10 942.43
1972-W11 939.87
1972-W12 942.88
1972-W13 942.28
1972-W14 940.70
1972-W15 962.60
1972-W16 967.72
1972-W17 963.80
1972-W18 954.17
1972-W19 941.23
1972-W20 941.83
1972-W21 961.54
1972-W22 971.25
1972-W23 961.39
1972-W24 934.45
1972-W25 945.06
1972-W26 944.69
1972-W27 929.03
1972-W28 938.06
1972-W29 922.26
1972-W30 920.45
1972-W31 926.70
1972-W32 951.76
1972-W33 964.18
1972-W34 965.83
1972-W35 959.36
1972-W36 970.05
1972-W37 961.24
1972-W38 947.23
1972-W39 943.03
1972-W40 953.27
1972-W41 945.36
1972-W42 930.46
1972-W43 942.81
1972-W44 946.42
1972-W45 984.12
1972-W46 995.26
1972-W47 1005.57
1972-W48 1025.21
1972-W49 1023.43
1972-W50 1033.19
1972-W51 1027.24
1972-W52 1004.21
1973-W01 1020.02
1973-W02 1047.49
1973-W03 1039.36
1973-W04 1026.19
1973-W05 1003.54
1973-W06 980.81
1973-W07 979.46
1973-W08 979.23
1973-W09 959.89
1973-W10 961.32
1973-W11 972.23
1973-W12 963.05
1973-W13 922.71
1973-W14 951.01
1973-W15 931.07
1973-W16 959.36
1973-W17 963.20
1973-W18 922.19
1973-W19 953.87
1973-W20 927.89
1973-W21 895.17
1973-W22 930.84
1973-W23 893.96
1973-W24 920.00
1973-W25 888.55
1973-W26 879.82
1973-W27 891.71
1973-W28 870.11
1973-W29 885.99
1973-W30 910.90
1973-W31 936.71
1973-W32 908.87
1973-W33 852.38
1973-W34 871.84
1973-W35 863.49
1973-W36 887.57
1973-W37 898.63
1973-W38 886.36
1973-W39 927.90
1973-W40 947.10
1973-W41 971.25
1973-W42 978.63
1973-W43 963.73
1973-W44 987.06
1973-W45 935.28
1973-W46 908.42
1973-W47 891.33
1973-W48 854.00
1973-W49 822.25
1973-W50 838.05
1973-W51 815.65
1973-W52 818.73
1974-W01 848.02
1974-W02 880.23
1974-W03 841.48
1974-W04 855.47
1974-W05 859.39
1974-W06 843.94
1974-W07 820.40
1974-W08 820.32
1974-W09 855.99
1974-W10 851.92
1974-W11 878.05
1974-W12 887.83
1974-W13 878.13
1974-W14 846.68
1974-W15 847.54
1974-W16 844.81
1974-W17 859.90
1974-W18 834.64
1974-W19 845.90
1974-W20 850.44
1974-W21 818.84
1974-W22 816.65
1974-W23 802.17
1974-W24 853.72
1974-W25 843.09
1974-W26 815.39
1974-W27 802.41
1974-W28 791.77
1974-W29 787.23
1974-W30 787.94
1974-W31 784.57
1974-W32 752.58
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
<title>Interactive SVG Charts With D3 – Part 2</title>
<link rel="stylesheet" type="text/css" href="./slides.css" />
<link rel="stylesheet" type="text/css" href="./prism.css" />
</head>
<body>
<div class="title">
<a href="http://artandlogic.com/2016/05/interactive-svg-charts-d3-part-2/">Interactive SVG Charts With D3 – Part 2</a>
</div>
<section id="slide">
<div class="heading"></div>
<div id="demo">
<div id="widgets"></div>
<button id="execute">Run Again</button>
</div>
<div class="content"></div>
</section>
<section id="src">
<div class="heading">Source</div>
</section>
<!-- TEMPLATES -->
<script id="links" type="text/template"><div class="links">
<a data-nav="prev" class="<%=prev_hidden%>" href="<%=prev%>"><%=prev_title%> &larr;</a>
<a data-nav="next" class="<%=next_hidden%>" href="<%=next%>">&rarr; <%=next_title%></a>
</div></script>
</script>
<!-- /TEMPLATES -->
<!-- SLIDES -->
<script type="text/slide"
data-title="Introduction"
data-function="intro"
data-highlight-lines="5,12,15">
<p>
This is Part 2 of a three part tutorial exploring building
interactive charts with <a href="http://d3js.org/">D3</a>.
</p>
<p>
You can find Part 1 at <a href="http://artandlogic.com/2016/05/interactive-svg-charts-d3-part-1/">Art &amp; Logic's blog site</a>.
</p>
<p>
In Part 2 we will add tool-tips for each data point on the
line. Try it now: hover your mouse over the line, when it is
over a data point, the point will appear and a tool-tip will
display the detail of the datum.
</p>
<p>
In this slide, we see the complete chart. It draws one line,
time series data of Dow Jones industrial averages, July 1971 -
August 1974. Placing your mouse pointer over the chart, then
scrolling the mouse wheel, you'll see the chart zoom-in or
zoom-out, centering the zoom action around your mouse
pointer. And, as mentioned above, hovering your mouse over a
point will activate a tool-tip.
</p>
<p>
In <strong>line 5</strong> in the javascript on the right, we
use d3 to select the svg element from the page, then
in <strong>line 12</strong>, instantiate a new Chart, passing
in the svg selection, it's width and height, along with the
data, which is an array of two-element arrays: [timestamp,
value]. In <strong>line 15</strong>, we call the rendering
method on the chart.
</p>
<p>
The rest of this tutorial explores the Render() method.
</p>
</script>
<script type="text/slide"
data-title="With No Tool-tip"
data-function="notooltip"
data-display-function="_notooltip"
data-highlight-lines="">
<p>
Before looking at how tool-tips are rendered, a quick
refresher from Part 1.
</p>
<p>
The chart on the right is exactly the same as the previous
slide's except there are no tool-tips. The javascript for
Part 1's Render() function is on the far right. (Full
discussion of this can be found
in <a href="http://bl.ocks.org/dpopowich/raw/cba9f20520f5164deec9f1d5dcba1a14/?s=1">Part 1</a>.)
</p>
<p>
The next slide will show the new <code>Render()</code> method,
which only has one small change.
</p>
</script>
<script type="text/slide"
data-title="With Tool-tip"
data-function="intro"
data-display-function="_render"
data-highlight-lines="13">
<p>
This chart restores the tool-tips. The only difference
between this slide and the previous is <strong>line
13</strong>: the output of <code>_DrawLine()</code> is
passed to a new method, <code>_Tip()</code>.
</p>
<p>
What is the output of <code>_Drawline()</code>? If you review
<a href="http://bl.ocks.org/dpopowich/raw/cba9f20520f5164deec9f1d5dcba1a14/?s=5">the
slide</a> discussing this method from Part 1, you will see it
returns the G element used to hold the PATH element that
renders our line. What's so special about the G element? It
is the element <em>bound with the data</em>. The library used
for generating the tool-tips expects a SVG or G element with
bound data.
</p>
</script>
<script type="text/slide"
data-title="_Tip() -- Dots ONLY"
data-function="tipdotsonly"
data-display-function="_tipdotsonly"
data-highlight-lines="5,8,10,19,23">
<p>
This slide demonstrates a slightly
modified <code>_Tip()</code> method: with vanilla D3 we will
show how the dots are drawn. When hovering over a data point,
only the circle will appear, not the tool-tip.
</p>
<p>
Recall that the G element passed in is bound to our data. In
<strong>line 5</strong> we create an update selection of
CIRCLE elements (which will be empty, there being no such
elements, yet). In <strong>line 8</strong> we use the enter
selection to append CIRCLE elements for each datum, setting
its radius to 5px (<strong>line 10</strong>) and centering the
circle on the x,y point using the chart's x- and y-scales.
The circles are filled with blue and, importantly, the opacity
of each is set to zero.
</p>
<p>
This is important to understand. For each datum, a circle
already exists, but you can't see it because the opacity is
zero. Use your browser's dev tools to explore the SVG; you
will notice hundreds of circle elements.
</p>
<p>
In <strong>lines 19 and 23</strong> we
set <code>mouseenter</code> and <code>mouseout</code> events on the
circles. The mouseenter sets the opacity to .5 so we can see
the circle while the mouseout resets the opacity to 0.
</p>
<p>
<strong>Lines 21, 25, and 28</strong> are commented out, which
prevents the tooltips from being displayed.
</p>
</script>
<script type="text/slide"
data-title="_Tip()"
data-function="intro"
data-display-function="_tip"
data-highlight-lines="21,25,28">
<p>
The only difference between this slide and the previous one
is <strong>lines 21, 25, and 28</strong> are now active and
the tooltips will be displayed. The next slide will discuss
this property.
</p>
</script>
<script type="text/slide"
data-title="D3-tip"
data-function="intro"
data-display-function="_inittip"
data-highlight-lines="7,8,9,16,19">
<p>
<a href="http://labratrevenge.com/d3-tip/">D3-tip</a> is an
add-on library for D3 to display tool-tips.
Complete <a href="https://github.com/Caged/d3-tip/blob/master/docs/index.md">API
documentation</a> can be found on github.
</p>
<p>
<code>d3.tip()</code> returns a function suitable for use with
D3's <a href="https://github.com/d3/d3/wiki/Selections#call">selection.call()</a>.
</p>
<p>
The function, assigned to <code>this.tip</code> (<strong>line
7</strong>), has a number of properties which affect how the
tool-tip is rendered.
</p>
<p>
In <strong>line 8</strong> we assign a class so we can control
the rendering of the underlying DIV of the tool-tip. The CSS
included has the following:
</p>
<pre>
<code>
.dot-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(204, 204, 204, 0.8);
color: blue;
border-radius: 12px;
}
</code>
</pre>
<p>
In <strong>line 9</strong> we set the offset of the tool-tip.
Each datum is passed to the function and by computing the x,y
points we can set the left and top of the DIV.
</p>
<p>
In <strong>line 16</strong> we set
the <a href="https://github.com/Caged/d3-tip/blob/master/docs/positioning-tooltips.md#tipdirection">direction</a>
to either South or North.
</p>
<p>
In <strong>line 19</strong> we set the HTML of the tool-tip,
which can be any arbitrary content. Here we build a simple
string, formatting the date using D3's
time <a href="https://github.com/d3/d3/wiki/Time-Formatting#format">format
utility function</a>. A more complicated example may use a
template rendering library to include a template which expects
values from each datum.
</p>
</script>
<script type="text/slide"
data-title="Using d3.tip() function"
data-function="intro"
data-display-function="_tip"
data-highlight-lines="21,25,28">
<p>
As stated in the previous slide, <code>d3.tip()</code> returns
a function that can be used with <code>.call()</code>. We see
this in <strong>line 28</strong>. This will create a DIV
(initially hidden) inserted into the BODY of the document.
</p>
<p>
When the <code>show()</code> function is called (<strong>line
21</strong>), the DIV will be positioned absolutely and
displayed. How it is displayed is determined by the
configuration we did when creating the tip function. When
the <code>hide()</code> function is called (<strong>line
25</strong>), the DIV is hidden.
</p>
<hr style="clear:both">
<p>
This ends Part 2 of the tutorial. In Part 3 we will explore
panning left and right using
D3's <a href="https://github.com/d3/d3/wiki/SVG-Controls#brush">brush
control</a>. <em>Stay tuned!</em>
</p>
<p>
Until then, please feel free to leave comments about the
tutorial
on <a href="https://artandlogic.com/2016/05/interactive-svg-charts-d3-part-2/">Art&amp;Logic's
blog post</a>.
</p>
</script>
<!-- /SLIDES -->
<!-- ------------------------------------------------------------ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"
charset="utf-8">
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.3/d3-tip.min.js" charset="utf-8"></script>
<script src="./prism.js" data-manual></script>
<script src="./demo_functions.js"></script>
<script src="./slides.js"></script>
<script>init_slides(d3, Prism, demo_functions);</script>
</body>
</html>
/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript&plugins=line-highlight+line-numbers */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre[data-line] {
position: relative;
padding: 1em 0 1em 3em;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em; /* Same as .prism’s padding-top */
background: hsla(24, 20%, 50%,.08);
background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
pointer-events: none;
line-height: inherit;
white-space: pre;
}
.line-highlight:before,
.line-highlight[data-end]:after {
content: attr(data-start);
position: absolute;
top: .4em;
left: .6em;
min-width: 1em;
padding: 0 .5em;
background-color: hsla(24, 20%, 50%,.4);
color: hsl(24, 20%, 95%);
font: bold 65%/1.5 sans-serif;
text-align: center;
vertical-align: .3em;
border-radius: 999px;
text-shadow: none;
box-shadow: 0 1px white;
}
.line-highlight[data-end]:after {
content: attr(data-end);
top: auto;
bottom: .4em;
}
pre.line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre.line-numbers > code {
position: relative;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript&plugins=line-highlight+line-numbers */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var o={};for(var s in i)if(i.hasOwnProperty(s)){if(s==n)for(var l in a)a.hasOwnProperty(l)&&(o[l]=a[l]);o[s]=i[s]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,n,a){for(var r in e)e.hasOwnProperty(r)&&(n.call(e,r,e[r],a||r),"Object"===t.util.type(e[r])?t.languages.DFS(e[r],n):"Array"===t.util.type(e[r])&&t.languages.DFS(e[r],n,r))}},plugins:{},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),i=0;a=r[i++];)t.highlightElement(a,e===!0,n)},highlightElement:function(a,r,i){for(var l,o,s=a;s&&!e.test(s.className);)s=s.parentNode;s&&(l=(s.className.match(e)||[,""])[1],o=t.languages[l]),a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,s=a.parentNode,/pre/i.test(s.nodeName)&&(s.className=s.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var u=a.textContent,g={element:a,language:l,grammar:o,code:u};if(!u||!o)return t.hooks.run("complete",g),void 0;if(t.hooks.run("before-highlight",g),r&&_self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){g.highlightedCode=n.stringify(JSON.parse(e.data),l),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(g.element),t.hooks.run("after-highlight",g),t.hooks.run("complete",g)},c.postMessage(JSON.stringify({language:g.language,code:g.code,immediateClose:!0}))}else g.highlightedCode=t.highlight(g.code,g.grammar,g.language),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(a),t.hooks.run("after-highlight",g),t.hooks.run("complete",g)},highlight:function(e,a,r){var i=t.tokenize(e,a);return n.stringify(t.util.encode(i),r)},tokenize:function(e,n){var a=t.Token,r=[e],i=n.rest;if(i){for(var l in i)n[l]=i[l];delete n.rest}e:for(var l in n)if(n.hasOwnProperty(l)&&n[l]){var o=n[l];o="Array"===t.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+"</"+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,i=n.immediateClose;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),i&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/<script[\w\W]*?>[\w\W]*?<\/script>/i,inside:{tag:{pattern:/<script[\w\W]*?>|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript;
!function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function t(e,t){return t=" "+t+" ",(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)>-1}function n(e,n,i){for(var o,l=n.replace(/\s+/g,"").split(","),a=+e.getAttribute("data-line-offset")||0,d=r()?parseInt:parseFloat,c=d(getComputedStyle(e).lineHeight),s=0;o=l[s++];){o=o.split("-");var u=+o[0],m=+o[1]||u,h=document.createElement("div");h.textContent=Array(m-u+2).join(" \n"),h.className=(i||"")+" line-highlight",t(e,"line-numbers")||(h.setAttribute("data-start",u),m>u&&h.setAttribute("data-end",m)),h.style.top=(u-a-1)*c+"px",t(e,"line-numbers")?e.appendChild(h):(e.querySelector("code")||e).appendChild(h)}}function i(){var t=location.hash.slice(1);e(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var i=(t.match(/\.([\d,-]+)$/)||[,""])[1];if(i&&!document.getElementById(t)){var r=t.slice(0,t.lastIndexOf(".")),o=document.getElementById(r);o&&(o.hasAttribute("data-line")||o.setAttribute("data-line",""),n(o,i,"temporary "),document.querySelector(".temporary.line-highlight").scrollIntoView())}}if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var r=function(){var e;return function(){if("undefined"==typeof e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding=0,t.style.border=0,t.innerHTML="&nbsp;<br />&nbsp;",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}}(),o=0;Prism.hooks.add("complete",function(t){var r=t.element.parentNode,l=r&&r.getAttribute("data-line");r&&l&&/pre/i.test(r.nodeName)&&(clearTimeout(o),e(".line-highlight",r).forEach(function(e){e.parentNode.removeChild(e)}),n(r,l),o=setTimeout(i,1))}),addEventListener("hashchange",i)}}();
!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+=" line-numbers");var n,a=e.code.match(/\n(?!$)/g),l=a?a.length+1:1,m=new Array(l+1);m=m.join("<span></span>"),n=document.createElement("span"),n.className="line-numbers-rows",n.innerHTML=m,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(n)}}})}();
html,body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Neue Helvetica", Helvetica, Arial, sans-serif;
font-size: 1em;
color: black;
background: white;
}
.title, .heading {
padding: 5px 0;
margin-bottom: 15px;
font-size: 30px;
font-weight: bold;
line-height: 1em;
text-align: center;
background: #81977F;
}
.heading {
position: fixed;
padding: 5px;
font-size: 24px;
text-align: left;
background: #ccc;
transform: translate(0, -40px);
}
.title a {
color: black;
text-decoration: none;
}
section {
display: inline-block;
margin-top: 40px;
border-right: 5px solid #81977F;
padding: 5px 15px;
height: calc(100% - 150px);
overflow: auto;
}
#demo {
float: right;
margin: 0 0 1em 1em;
text-align: center;
}
#slide {
width: calc(60% - 35px);
}
#slide code {
padding: 2px 5px;
background: #ddd;
}
#slide pre code {
background: inherit;
}
#src {
border-right: none;
width: calc(40% - 50px);
}
.hidden {
visibility: hidden;
}
.links {
margin: 10px 0;
clear: both;
}
.links a {
font-size: 1.2em;
}
.links a[data-nav=next] {
float: right;
}
svg {
display: block;
margin: 5px auto;
border: 1px solid black;
}
#execute {
margin-top: 5px;
}
.moveable {
cursor: move;
}
.axis {
font-size: .8em;
}
.axis line {
stroke: #333;
}
.axis path {
shape-rendering: crispEdges;
fill: none;
stroke: #333;
}
.dot-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(204, 204, 204, 0.8);
color: blue;
border-radius: 12px;
}
/*jslint browser: true, indent: 3, white: true, nomen: true */
function init_slides(d3, prism, function_generator) {
"use strict";
var slides = d3.selectAll('script[type="text/slide"]'),
el_slide = d3.select('#slide'),
el_demo = d3.select('#demo'),
el_src = d3.select('#src'),
el_execute = d3.select('#execute'),
el_widgets = d3.select('#widgets'),
LINKS_TMPL = d3.select('#links').html(),
WIDTH = 600, // svg width
HEIGHT = 400,// svg height
slide_func = null,
current = 0,
minslide = 0,
maxslide = slides.size()-1,
disable_run_again = function() {
/* disable the "Run Again" button */
el_execute.property('disabled', true);
},
enable_run_again = function() {
/* enable the "Run Again" button */
el_execute.property('disabled', false);
},
functions = function_generator(d3, WIDTH, HEIGHT, el_widgets,
disable_run_again, enable_run_again);
function verify() {
/* verify each slide has all required attributes and a defined
* function */
slides.each(function(d, i) {
/*jslint unparam: true */
var el = this,
funcname = el.getAttribute('data-function'),
displayfunc = el.getAttribute('data-display-function');
if (!el.hasAttribute('data-title')) {
console.error('Slide #' + (i+1) + ' does not have required attribute: data-title');
}
if (!funcname) {
console.error('Slide #' + (i+1) + ' does not have required attribute: data-function');
}
if (funcname && (functions[funcname] === undefined)) {
console.error('Slide #' + (i+1) + ' specified function is not defined: ' + funcname);
}
if (displayfunc && displayfunc !== '<none>' && (functions[displayfunc] === undefined)) {
console.error('Slide #' + (i+1) + ' specified display-function is not defined: ' + displayfunc);
}
});
}
function execute() {
/* set up svg element and execute the function */
var widgetsNode;
// empty #widgets
widgetsNode = el_widgets.node();
while (widgetsNode.firstChild) {
widgetsNode.removeChild(widgetsNode.firstChild);
}
// remove/re-add <svg>
el_demo.select('svg').remove();
el_demo.insert('svg', '#widgets')
.attr({width: WIDTH, height: HEIGHT});
slide_func();
}
function go(slide) {
/* go to a slide (where: 0 <= slide < slides.size()) */
var tmpl, title, highlight, hidden, links, linksNode, display_func;
// confirm sanity
if (slide < minslide || slide > maxslide) {
console.error('Illegal slide: #' + slide);
current = 0;
}
// previous button
hidden = (slide === minslide);
links = LINKS_TMPL.replace(/<%=prev_hidden%>/g, hidden ? "hidden" : "");
if (!hidden) {
title = slides[0][slide-1].getAttribute('data-title');
links = links.replace(/<%=prev%>/g, slide-1);
links = links.replace(/<%=prev_title%>/g, title);
}
// next button
hidden = (slide === maxslide);
links = links.replace(/<%=next_hidden%>/g, hidden ? "hidden" : "");
if (!hidden) {
title = slides[0][slide+1].getAttribute('data-title');
links = links.replace(/<%=next%>/g, slide+1);
links = links.replace(/<%=next_title%>/g, title);
}
// the template
tmpl = d3.select(slides[0][slide]);
title = tmpl.attr('data-title');
highlight = tmpl.attr('data-highlight-lines');
// the slide function
slide_func = functions[tmpl.attr('data-function')];
// the display-function
display_func = tmpl.attr('data-display-function');
if (display_func) {
if (display_func !== '<none>') {
display_func = functions[display_func];
}
} else {
// by defualt we display the slide function
display_func = slide_func;
}
// *** set the slide content ***
// remove any previous links
el_slide.selectAll('.links').remove();
// add title
el_slide.select('.heading').html(title);
// create node from links html
var linksNode = document.createElement('div');
linksNode.innerHTML = links;
linksNode = linksNode.firstChild;
// insert the links node before the SVG demo
el_slide.node().insertBefore(linksNode, el_demo.node());
// insert the content html
el_slide.select('.content').html(tmpl.html());
// append the links node, so it's at the bottom, too.
el_slide.node().appendChild(linksNode.cloneNode(true));
// *** set source and highlight it ***
if (display_func === '<none>') {
// no display function specified
el_src.style('display', 'none');
} else {
// make sure the src is displayed
el_src.style('display', null);
// remove existing <pre>
el_src.select('pre').remove();
// append new, highlighted <pre><code>...</code></pre>
prism.highlightElement(
el_src.append('pre')
.classed('line-numbers', true)
.attr('data-line', highlight)
.append('code')
.classed('language-javascript', true)
.html(display_func.toString()
.replace(/function\s*\(\)[^]*?\/\*/, 'function() { /*')
.replace(/^ {6}/gm, ''))
.node());
}
// execute function (which sets up SVG element)
execute();
}
function init() {
/* initialize events, etc. */
var s;
// clicking a nav link -- set up listener using delegation
d3.select('#slide')
.on('click', function() {
var e = d3.event, target = e.target;
// only interested in anchors with data-nav attribute
if (! (target.nodeName === 'A' && target.hasAttribute('data-nav'))) {
return;
}
// prevent following link
e.preventDefault();
// do the math to compute the next slide
if (target.getAttribute('data-nav') === 'prev') {
current -= 1;
} else {
current += 1;
}
// allow back/forward buttons to work
window.history.pushState({slide:current}, target.textContent, '?s=' + current);
// go to the new current slide
go(current);
});
window.addEventListener('popstate', function(e) {
var state = e.state;
// set the current slide from the state, defaulting to the
// first slide
current = (state && state.slide) || 0;
// go to the new current slide
go(current);
});
// location.search indicates slide??
// regex looking for ?s=SLIDE... or ...&s=SLIDE...
s = /[&?]s=([0-9]+)/.exec(location.search);
if (s !== null) {
// match found, the slide number will be the 2nd element of
// the returned array
current = + s[1];
}
// clicking Run Again button
el_execute.on('click', execute);
}
// verify each slide is valid
verify();
// initialize global event handlers
init();
// go to the current slide
go(current);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment