|
|
|
// load/format |
|
d3.csv('movie.csv', type, function(err, data) { |
|
|
|
var data = data; |
|
|
|
// Choose the main quant variable the chart displays (and update the button accordingly) |
|
var xVariable = 'budget'; |
|
d3.select('button#sort1').html('sort by ' + xVariable); |
|
|
|
// Compose the chart |
|
var newChart = chart() |
|
.xVar(xVariable); |
|
|
|
// Initialise the chart |
|
d3.select('div.container') |
|
.datum(data) |
|
.call(newChart); |
|
|
|
|
|
// Change the data option 1 |
|
d3.select('#sort1').on('mousedown', function(d) { |
|
|
|
data = _.sortBy(data, function(el) { return el[xVariable]; }); |
|
|
|
d3.select('div.container') |
|
.datum(data) |
|
.call(newChart); |
|
|
|
}); |
|
|
|
// Change the data option 2 |
|
d3.select('#sort2').on('mousedown', function(d) { |
|
|
|
data = _.sortBy(data, function(el) { return el.film; }); |
|
|
|
d3.select('div.container') |
|
.datum(data) |
|
.call(newChart); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
// Chart function |
|
function chart() { |
|
|
|
// Exposed variables |
|
|
|
var width = 700; |
|
var height = 400; |
|
|
|
var xVar = 'rating'; |
|
var yVar = 'film'; |
|
|
|
// Closure to hide mechanics |
|
function my(selection) { |
|
|
|
// pass the data to each selection (multiples-friendly) |
|
selection.each(function(data, i) { |
|
|
|
var minX = 0; |
|
var maxX = d3.max(data, function(d) { return d[xVar]; }); |
|
|
|
var scaleX = d3.scale.linear().domain([minX, maxX]).range([0, width]); |
|
var scaleY = d3.scale.ordinal().domain(d3.range(data.length)).rangePoints([height, 0], 1); |
|
|
|
|
|
// In the following we'll attach an svg element to the container element (the 'selection') when and only when we run this the first time. |
|
// We do this by using the mechanics of the data join and the enter selection. |
|
// As a short reminder: the data join (on its own, not chained with .enter()) checks how many data items there are |
|
// and stages a respective number of DOM elements. |
|
// An join on its own - detached from the .enter() method checks first how many data elements come in new |
|
// (n = new data elements) to the data join selection and then it appends the specified DOM element exactly n times. |
|
|
|
// Here we do exactly that with joining the data as one array element with the non-existing svg first: |
|
|
|
var svg = d3.select(this) // conatiner (here 'body') |
|
.selectAll('svg') // first time: empty selection of staged svg elements (it's .selectAll not .select) |
|
.data([data]); // first time: one array item, hence one svg will be staged (but not yet entered); |
|
|
|
svg // one data item [data] staged with one svg element |
|
.enter() // first time: initialise the DOM element entry; second time+: empty |
|
.append("svg"); // first time: append the svg; second time+: nothing happens |
|
|
|
// If we have more elements apart from the svg element that should only be appended once to the chart |
|
// like axes, or svg > g-elements for the margins, |
|
// we would store the enter-selection in a unique variable (like 'svgEnter', or if we inlcude another g 'gEnter'. |
|
// This allows us to reference just the enter()-selection which would be empty with every update, |
|
// not invoking anything that comes after .enter() - apart from the very first time. |
|
|
|
svg |
|
.attr('width', width) |
|
.attr('height', height); |
|
|
|
|
|
// Here comes the general update pattern: |
|
|
|
// Data join |
|
var bar = svg // this cost me a night ! see below for explanations. |
|
.selectAll('.bar') |
|
.data(data, function(d) { return d[yVar]; }); // key function to achieve object constancy |
|
|
|
|
|
// I select the svg via the svg variable. This does not work in V4 anymore - I have to select the svg with a simple selector (d3.select('svg')) |
|
// Why? Because the enter selection in v4 is immutable - it doesn't change and doesn't automatically cover updates. |
|
// In v3.x it was still mutable and covered any updates on the element as well. |
|
// console.log the variable and the selector in v3.x and in v4 and see that in v4 the 2 actually differ. |
|
// The var has enter and exit functions, the d3.select has not. |
|
// in v3.x both have enter and exit functions B U T an enter selection in v3.x was open to changes |
|
// like for example appending elemenst - like we do here with the rects. |
|
// In v4 the enter selection is immutable which means we can't append anything unless it's the first data join of the page loading period. |
|
// (or unless we tell the enter selection to merge with the update selection with .merge()) |
|
// In our case, however, the best way is to just take that svg as a simple manifested selection and do with it whatever we want. |
|
|
|
|
|
// Enter |
|
bar |
|
.enter() |
|
.append('rect') |
|
.classed('bar', true) |
|
.attr('x', scaleX(minX)) |
|
.attr('height', 5) |
|
.attr('width', function(d) { return scaleX(minX); }); |
|
|
|
// Update |
|
bar |
|
.transition().duration(1000).delay(function(d,i) { return i / (data.length-1) * 1000; }) // implement gratuitous object constancy |
|
.attr('width', function(d) { return scaleX(d[xVar]); }) |
|
.attr('y', function(d, i) { return scaleY(i); }); |
|
|
|
// Exit |
|
bar |
|
.exit() |
|
.transition().duration(1000) |
|
.attr('width', function(d) { return scaleX(minX); }) |
|
.remove(); |
|
|
|
}); // selection.each() |
|
|
|
triggerTooltip(yVar); // invoke tooltip - not necessary, forget about it, remove it to keep it simple |
|
|
|
} // Closure |
|
|
|
|
|
// Accessor functions for exposed variables |
|
|
|
my.xVar = function(value) { |
|
if(!arguments.length) return xVar; |
|
xVar = String(value); |
|
return my; |
|
} |
|
|
|
my.yVar = function(value) { |
|
if(!arguments.length) return yVar; |
|
yVar = String(value); |
|
return my; |
|
} |
|
|
|
my.width = function(value) { |
|
if(!arguments.length) return width; |
|
width = value; |
|
return my; |
|
} |
|
|
|
my.height = function(value) { |
|
if(!arguments.length) return height; |
|
height = value; |
|
return my; |
|
} |
|
|
|
|
|
return my; // Expose closure |
|
|
|
} // chart() |
|
|
|
|
|
|
|
|
|
// Format data |
|
function type(d) { |
|
d.film = d.film; |
|
d.rating = +d.rating; |
|
return d; |
|
} |
|
|
|
|
|
// Tooltip (not key, but kind) |
|
var triggerTooltip = function(yVar) { |
|
|
|
d3.selectAll('.bar').on('mouseover', function(d) { |
|
|
|
var datapoint = d3.select(this).data()[0]; |
|
|
|
d3.select('div.tooltip') |
|
.style('left', (d3.event.pageX + 5) + 'px') |
|
.style('top', (d3.event.pageY + 5) + 'px') |
|
.html(datapoint[yVar]) |
|
.style('opacity', 0) |
|
.transition() |
|
.style('opacity', .9); |
|
|
|
}); |
|
|
|
d3.selectAll('.bar').on('mousemove', function(d) { |
|
|
|
d3.select('div.tooltip') |
|
.style('left', (d3.event.pageX + 5) + 'px') |
|
.style('top', (d3.event.pageY + 5) + 'px'); |
|
}); |
|
|
|
d3.selectAll('.bar').on('mouseout', function(d) { |
|
|
|
d3.select('div.tooltip') |
|
.transition() |
|
.style('opacity', 0); |
|
|
|
}); |
|
|
|
}; |