|
/*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 {...}; |
|
} |