|
'use strict'; |
|
|
|
// ***************************************** |
|
// reusable multiple slopegraph chart |
|
// ***************************************** |
|
|
|
(function () { |
|
|
|
d3.slopegraph = function module() { |
|
|
|
// default variables |
|
var w = 150, |
|
h = 70, |
|
margin = { top: 40, bottom: 40, left: 80, right: 80 }, |
|
gutter = 50, |
|
strokeColour = 'black', |
|
|
|
// key data values (in order) |
|
keyValues = [], |
|
|
|
// category (default for key) |
|
type = "key", |
|
|
|
// array with total number of categories |
|
categories = [], |
|
format = d3.format(''), |
|
sets = 0; |
|
|
|
var dispatch = d3.dispatch('_hover'); |
|
var svg, yScale; |
|
|
|
function exports(_selection) { |
|
var _this = this; |
|
|
|
_selection.each(function (data) { |
|
|
|
var allValues = []; |
|
|
|
// get number of categories |
|
categories = _.uniq(data.map(function (d) { |
|
return d.values[type]; |
|
})); |
|
// checks if no category has been selected |
|
categories = categories.length == 1 ? _.uniq(data.map(function (d) { |
|
return d.key; |
|
})) : categories; |
|
|
|
// get dataset lengths |
|
data.forEach(function (d) { |
|
_.times(keyValues.length, function (n) { |
|
var year = keyValues[n]; |
|
allValues.push(d.values.count[year]); |
|
}); |
|
}); |
|
|
|
// create max value so scale is consistent |
|
var maxValue = _.max(allValues); |
|
// adapt the size against number of sets |
|
w = w * keyValues.length; |
|
h = h * categories.length; |
|
// have reference for number of sets |
|
sets = keyValues.length - 1; |
|
// use same scale for both sides |
|
yScale = d3.scale.linear().domain([0, maxValue]).range([h - margin.top, margin.bottom]); |
|
|
|
// clean start |
|
// WARN: es2015 babel wrongly transpiles 'this' |
|
d3.select(this).select('svg').remove(); |
|
|
|
svg = d3.select(this).append('svg').attr("width", w).attr("height", h); |
|
|
|
render(data, 0); |
|
}); |
|
} |
|
|
|
// recursive function to apply each set |
|
// then the start and end labels (as only needed once) |
|
function render(data, n) { |
|
if (n < keyValues.length - 1) { |
|
lines(data, n); |
|
middleLabels(data, n); |
|
return render(data, n + 1); |
|
} else { |
|
startLabels(data); |
|
endLabels(data); |
|
return n; |
|
} |
|
} |
|
|
|
// render connecting lines |
|
function lines(data, n) { |
|
/* |
|
s-line-{n} toggles column values (1st column, 2nd column, ...) |
|
sel-${cat.indexOf(d)} toggles category values (cat1, cat2, ...) |
|
sel-ind-${i} toggles line values (obj1, obj2, ...) |
|
*/ |
|
|
|
var lines = svg.selectAll('.s-line-' + n).data(data).enter().append('line'); |
|
|
|
lines.attr({ |
|
x1: function x1() { |
|
if (n === 0) { |
|
return margin.left; |
|
} else { |
|
return w / sets * n + margin.left / 2; |
|
} |
|
}, |
|
y1: function y1(d) { |
|
return yScale(d.values.count[keyValues[n]]); |
|
}, |
|
x2: function x2() { |
|
if (n === sets - 1) { |
|
return w - margin.right; |
|
} else { |
|
return w / sets * (n + 1) - gutter; |
|
} |
|
}, |
|
y2: function y2(d) { |
|
return yScale(d.values.count[keyValues[n + 1]]); |
|
}, |
|
stroke: strokeColour, |
|
'stroke-width': 1, |
|
class: function _class(d, i) { |
|
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH |
|
return 'elm s-line-' + n + ' sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i; |
|
} |
|
}).on('mouseover', dispatch._hover); |
|
} |
|
|
|
// middle labels in-between sets |
|
function middleLabels(data, n) { |
|
|
|
if (n !== sets - 1) { |
|
var middleLabels = svg.selectAll('.m-labels-' + n).data(data); |
|
|
|
middleLabels.enter().append('text').attr({ |
|
class: function _class(d, i) { |
|
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH |
|
return 'labels m-labels-' + n + ' elm sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i; |
|
}, |
|
x: w / sets * (n + 1) + 15, |
|
y: function y(d) { |
|
return yScale(d.values.count[keyValues[n + 1]]) + 4; |
|
} |
|
}).text(function (d) { |
|
return format(d.values.count[keyValues[n + 1]]); |
|
}).style('text-anchor', 'middle').on('mouseover', dispatch._hover); |
|
|
|
// title |
|
svg.append('text').attr({ |
|
class: 's-title', |
|
x: w / sets * (n + 1), |
|
y: margin.top / 2 |
|
}).text(keyValues[n + 1] + ' \u2193').style('text-anchor', 'middle'); |
|
} |
|
} |
|
|
|
// start labels applied left of chart sets |
|
function startLabels(data) { |
|
|
|
var startLabels = svg.selectAll('.l-labels').data(data); |
|
|
|
startLabels.enter().append('text').attr({ |
|
class: function _class(d, i) { |
|
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH |
|
return 'labels l-labels elm sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i; |
|
}, |
|
x: margin.left - 3, |
|
y: function y(d) { |
|
return yScale(d.values.count[keyValues[0]]) + 4; |
|
} |
|
}).text(function (d) { |
|
return d.key + ' ' + format(d.values.count[keyValues[0]]); |
|
}).style('text-anchor', 'end').on('mouseover', dispatch._hover); |
|
|
|
// title |
|
svg.append('text').attr({ |
|
class: 's-title', |
|
x: margin.left - 3, |
|
y: margin.top / 2 |
|
}).text(keyValues[0] + ' \u2193').style('text-anchor', 'end'); |
|
} |
|
|
|
// end labels applied right of chart sets |
|
function endLabels(data) { |
|
|
|
var end = keyValues.length - 1; |
|
|
|
var endLabels = svg.selectAll('r.labels').data(data); |
|
|
|
endLabels.enter().append('text').attr({ |
|
class: function _class(d, i) { |
|
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH |
|
return 'labels r-labels elm sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i; |
|
}, |
|
x: w - margin.right + 3, |
|
y: function y(d) { |
|
return yScale(d.values.count[keyValues[end]]) + 4; |
|
} |
|
}).text(function (d) { |
|
return d.key + ' ' + format(d.values.count[keyValues[end]]); |
|
}).style('text-anchor', 'start').on('mouseover', dispatch._hover); |
|
|
|
// title |
|
svg.append('text').attr({ |
|
class: 's-title', |
|
x: w - margin.right + 3, |
|
y: margin.top / 2 |
|
}).text('\u2193 ' + keyValues[end]).style('text-anchor', 'start'); |
|
} |
|
|
|
// getter/setters for overrides |
|
exports.w = function (value) { |
|
if (!arguments.length) return w; |
|
w = value; |
|
return this; |
|
}; |
|
exports.h = function (value) { |
|
if (!arguments.length) return h; |
|
h = value; |
|
return this; |
|
}; |
|
exports.margin = function (value) { |
|
if (!arguments.length) return margin; |
|
margin = value; |
|
return this; |
|
}; |
|
exports.gutter = function (value) { |
|
if (!arguments.length) return gutter; |
|
gutter = value; |
|
return this; |
|
}; |
|
exports.format = function (value) { |
|
if (!arguments.length) return format; |
|
format = value; |
|
return this; |
|
}; |
|
exports.strokeColour = function (value) { |
|
if (!arguments.length) return strokeColour; |
|
strokeColour = value; |
|
return this; |
|
}; |
|
exports.keyValues = function (value) { |
|
if (!arguments.length) return keyValues; |
|
keyValues = value; |
|
return this; |
|
}; |
|
exports.category = function (value) { |
|
if (!arguments.length) return category; |
|
type = value; |
|
return this; |
|
}; |
|
|
|
d3.rebind(exports, dispatch, 'on'); |
|
return exports; |
|
}; |
|
})(); |