jquery.flot.pyramid.js | |
---|---|
This is a plugin for drawing population pyramids with flot. | |
Ok, let's wrap everything in a warm, secure closure. | var FlotPyramid = (function(){ |
Some declarations follow: | |
The yaxis tick values will be stored in here. | var yaxisTicks = [], |
An object which will be thrown when the data to plot is invalid. | InvalidData = {
plugin: 'flot.pyramid',
msg: 'Invalid series for pyramid plot! The supplied data must have exactly the same labels!'
}, |
And another one which will be thrown when any of the data series directions specified by the user has a wrong value. | InvalidDirection = {
plugin: 'flot.pyramid',
msg: 'Invalid direction specified for pyramid series. Use \'L\' or \'W\' for left, or \'R\' or \'E\' for right (default is right)'
} |
Flot plumbing | |
Hooks the pyramid plugin options processor when flot initializes the plot. | function init(plot) {
plot.hooks.processOptions.push(processOptions);
} |
Processes the plot options. | function processOptions(plot, options) { |
When the pyramid plugin is active (i.e. shown) | if (options.series.pyramid && options.series.pyramid.show) { |
Configure flot options in order to plot the data using bars, extending any user defined bars options to get hotizontal, centered bars. If the user supplies a custom barWidth, use it. | $.extend(options.series.bars, {
show: true,
horizontal: true,
align: 'center',
barWidth: options.series.pyramid.barWidth || 0.6
});
var xaxis = options.xaxes[options.series.xaxis - 1 || 0]; |
Configure the custom pyramid X axis tick formatter, preserving the user defined one, if any. | $.extend(xaxis, {
tickFormatter: xaxisTickFormatter(xaxis.tickFormatter)
});
var yaxis = options.yaxes[options.series.yaxis - 1 || 0]; |
Use custom Y axis ticks. | $.extend(yaxis, {
ticks: yaxisTicks
}); |
Register the pyramid raw data processor... | plot.hooks.processRawData.push(processRawData); |
... and the pyramid data points processor. | plot.hooks.processDatapoints.push(processDatapoints);
}
} |
Processes the data as-is | function processRawData(plot, series, datapoints) { |
The pyramid plugin needs to change the data label values, so, in order to preserve the original data, a deep clone is made | series.data = $.extend(true, [], series.data); |
Now the Y axis can be fixed using the series data. | fixYaxis(series.data); |
And also the X axis. | fixXaxis(plot.getOptions(), series);
} |
Processes the data points generated by flot. | function processDatapoints(plot, series, datapoints) {
var swapped = [],
points = datapoints.points, |
Calculate the multiplier factor given the data plot direction (left or west values must be drawn towards the negative part of the X axis) | mult = (series.pyramid.direction || 'R').match(/L|W/) ? -1 : 1; |
For each data point, originally in the form (X,Y,whatever), swap the X and Y axis values, giving the X value the right direction using the previously calculated multiplier. | for(var i = 0, len = points.length; i < len; i += datapoints.format.length) {
swapped.push(points[i+1] * mult);
swapped.push(points[i]);
swapped.push(points[i+2]);
} |
Complete the swap! | datapoints.points = swapped;
} |
X axis formatting | |
Formats the X axis tick values | function xaxisTickFormatter(oldFormatter) {
return function(val, axis) { |
turning negative values into positive ones. | val = val < 0 ? -val: val; |
Returns the formatted (abs) value, optionally formatted through the original user-supplied formatter (that's a formatting party!). | return oldFormatter ? oldFormatter(val, axis) : val;
}
} |
Fixes the X axis values in the series. | function fixXaxis(options, series) {
var max,
currentMax = options.xaxes[0].max || 0,
data = series.data,
values; |
Check whether the series direction declaration is OK. | checkSeriesDirection(series); |
Define a helper function which reduces the given data applying f. | function reduce(data, f) {
return data.reduce(function(prev, current, index, array) {
return f(prev, current);
});
} |
Get the maximum value in the series data. | values = data.map(function(d){return d[1]});
max = reduce(values, Math.max); |
Compare the maximum value to the global maximum for the axis. TODO: replace that 0 with the relevant axis index | options.xaxes[0].max = Math.max(max, currentMax); |
Set the minimum to -maximum in order to get a symmetrical chart. | options.xaxes[0].min = -options.xaxes[0].max;
} |
Checks the series direction declaration, if present. | function checkSeriesDirection(series) {
var direction = series.pyramid.direction; |
And if, indeed, it is present, and has an unknown value | if (direction && !direction.match(/L|W|R|E/)) { |
throw InvalidDirection. | throw(InvalidDirection);
}
} |
Y axis formatting | |
Fixes the Y axis values in the data and fixes the Y axis ticks values. | function fixYaxis(data) { |
If this is the first series to be processed (and therefore the yaxisTicks array is empty) | if (yaxisTicks.length == 0) { |
use it to extract the tick values. All the data labels (which will be used as tick values) in all the plotted series must be exactly the same, | extractTicks(data);
} else { |
so, if the data labels have already been extracted, every other series' data is checked. | checkTicks(data);
} |
Once extracted (or checked) the labels, the data can be rewritten in order to be plotted. | rewriteTicks(data);
} |
Extracts the Y axis ticks given the data to be plotted, | function extractTicks(data) {
var i, len;
for(i = 0, len = data.length; i < len; i += 1) { |
extracting the data label and building an array in the form [[1, "0-10"], [2, "10-20"],..., [9, "90+"]] | yaxisTicks.push([i, data[i][0]]);
}
} |
Checks whether the given data is "well formed", | function checkTicks(data) { |
i.e. it has so many data values as Y axis ticks, and every data value is labeled with a label which is present in the Y axis ticks (labels) array. Wow. | if (!sameTicksLength(data) || !allTicksPresent(data)) { |
Oh, if something is wrong with the data, just throw InvalidData. | throw(InvalidData);
}
} |
Checks whether the given data has the same length as the Y axis extracted ticks array. | function sameTicksLength(data) {
return yaxisTicks.length == data.length;
} |
Checks whether every tick in the Y axis has a corresponding labeled data entry in the data array. | function allTicksPresent(data) {
var labels, expected_labels; |
Get the actual and expected labels | expected_labels = $.map(yaxisTicks, function(e) { return e[1] });
labels = $.map(data, function(e) { return e[0] }); |
And compare them. | return expected_labels.toString() == labels.toString();
} |
Rewrites the data label values, | function rewriteTicks(data) { |
removing the label and replacing it with the data value ordinal. | for (var i = 0, len = data.length; i < len; i+= 1) {
data[i][0] = i;
}
} |
Wrap it up! | |
Expose only the minimum stuff, i.e. | return { |
The init function... | init: init, |
And the InvalidDirection and InvalidData "exception" objects... | InvalidDirection: InvalidDirection,
InvalidData: InvalidData
}
}()); |
Once the document is ready, add the plugin to the list of available flot plugins. | (function ($) {
$.plot.plugins.push({
init: FlotPyramid.init,
name: "pyramid",
version: "1.0.0"
});
})(jQuery);
|