--- a/ckanext/dga_stats/public/ckanext/stats/javascript/modules/plot.js +++ b/ckanext/dga_stats/public/ckanext/stats/javascript/modules/plot.js @@ -1,1 +1,210 @@ - +/* A quick module for generating flot charts from an HTML table. Options can + * be passed directly to flot using the data-module-* attributes. The tables + * are currently expected to be marked up as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * ... + * + *
X AxisSeries A LegendSeries B Legend
X ValueSeries A Y ValueSeries B Y Value
+ * + * Points are pulled out of the th/td elements using innerHTML or by looking + * for a data-value attribute. This is useful when a more readable value + * needs to be used in the elements contents (eg. dates). A data-type attribute + * can also be applied to parse the value. Only data-type="date" is currently + * supported and expects data-value to be a unix timestamp. + */ +this.ckan.module('plot', function (jQuery, _) { + return { + /* Holds the jQuery.plot() object when created */ + graph: null, + + /* Holds the canvas container when created */ + canvas: null, + + /* Default options */ + options: { + xaxis: {}, + yaxis: {}, + legend: {position: 'nw'}, + colors: ['#ffcc33', '#ff8844'] + }, + + /* Sets up the canvas element and parses the table. + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/); + + if (!this.el.is('table')) { + throw new Error('CKAN module plot can only be called on table elements'); + } + + this.setupCanvas(); + + // Because the canvas doesn't render correctly unless visible we must + // listen for events that reveal the canvas and then try and re-render. + // Currently the most common of these is the "shown" event triggered by + // the tabs plugin. + this.sandbox.body.on("shown", this._onShown); + this.data = this.parseTable(this.el); + + this.draw(); + }, + + /* Removes event handlers when the module is removed from the DOM. + * + * Returns nothing. + */ + teardown: function () { + this.sandbox.body.off("shown", this._onShown); + }, + + /* Creates the canvas wrapper and removes the table from the document. + * + * Returns nothing. + */ + setupCanvas: function () { + this.canvas = jQuery('
'); + this.el.replaceWith(this.canvas); + }, + + /* Attempts to draw the chart if the canvas is visible. If not visible the + * graph does not render correctly. So we keep trying until it is. + * + * Examples + * + * module.draw(); + * + * Returns nothing. + */ + draw: function () { + if (!this.drawn && this.canvas.is(':visible')) { + this.graph = jQuery.plot(this.canvas, this.data, this.options); + } + }, + + /* Parses an HTML table element to build the data array for the chart. + * The thead element provides the axis and labels for the series. The + * first column in the tbody is used for the x-axis and subsequent + * columns are the series. + * + * table - A table element to parse. + * + * Examples + * + * module.parseTable(module.el); + * + * Returns data object suitable for use in jQuery.plot(). + */ + parseTable: function (table) { + var data = []; + var _this = this; + + var headings = table.find('thead tr:first th').map(function () { + return this.innerHTML; + }); + + table.find('tbody tr').each(function (row) { + var element = jQuery(this); + var x = []; + + x[row] = _this.getValue(element.find('th')); + + element.find('td').each(function (series) { + var value = _this.getValue(this); + var label = headings[series + 1]; + + data[series] = data[series] || {data: [], label: label}; + data[series].data[row] = [x[row], value]; + }); + }); + + return data; + }, + + /* Retrieves the value from a td/th element. This first looks for a + * data-value attribute on the element otherwise uses the element + * text contents. + * + * A data-type attribute can also be provided to tell the module how + * to deal with the element. By default we let jQuery.data() handle + * the parsing but this can provide additional data. See .parseValue() + * for more info. + * + * cell - An element to extract a value from. + * + * Examples + * + * var element = jQuery('Ten'); + * module.getValue(element); //=> 10 + * + * var element = jQuery('20'); + * module.getValue(element); //=> 20 + * + * var element = jQuery('1343747094'); + * module.getValue(element); //=> + * + * Returns the parsed value. + */ + getValue: function (cell) { + var item = cell instanceof jQuery ? cell : jQuery(cell); + var type = item.data('type') || 'string'; + var value = item.data('value') || item.text(); + return this.parseValue(value, type); + }, + + /* Provides the ability to further format a value. + * + * If date is provided as a type then it expects value to be a unix + * timestamp in seconds. + * + * value - The value extracted from the element. + * type - A type string, currently only supports "date". + * + * Examples + * + * module.parseValue(10); // => 10 + * module.parseValue("cat"); // => "cat" + * module.parseValue(1343747094, 'date'); // => + * + * Returns the parsed value. + */ + parseValue: function (value, type) { + if (type === 'date') { + value = new Date(parseInt(value, 10) * 1000); + if (!value) { + value = 0; + } + } + return value; + }, + + /* Event handler for when tabs are toggled. Determines if the canvas + * resides in the shown element and attempts to re-render. + * + * event - The shown event object. + * + * Returns nothing. + */ + _onShown: function (event) { + if (!this.drawn && jQuery.contains(jQuery(event.target.hash)[0], this.canvas[0])) { + this.draw(); + } + } + }; +}); +