Add summary and activity screens, remove private datasets from counts
[ckanext-dga-stats.git] / ckanext / dga_stats / public / ckanext / stats / javascript / modules / plot.js
blob:a/ckanext/dga_stats/public/ckanext/stats/javascript/modules/plot.js -> blob:b/ckanext/dga_stats/public/ckanext/stats/javascript/modules/plot.js
--- 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:
+ *
+ *   <table data-module="plot">
+ *     <thead>
+ *       <tr>
+ *         <th>X Axis</th>
+ *         <th>Series A Legend</th>
+ *         <th>Series B Legend</th>
+ *       </tr>
+ *     </thead>
+ *     <tbody>
+ *       <tr>
+ *         <th>X Value</th>
+ *         <td>Series A Y Value</td>
+ *         <td>Series B Y Value</td>
+ *       </tr>
+ *       ...
+ *     </tbody>
+ *   </table>
+ *
+ * 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('<div class="module-plot-canvas">');
+      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('<td data-value="10">Ten</td>');
+     *   module.getValue(element); //=> 10
+     *
+     *   var element = jQuery('<td>20</td>');
+     *   module.getValue(element); //=> 20
+     *
+     *   var element = jQuery('<td data-type="date">1343747094</td>');
+     *   module.getValue(element); //=> <Date Tue Jul 31 2012 16:04:54 GMT+0100 (BST)>
+     *
+     * 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'); // => <Date Tue Jul 31 2012 16:04:54 GMT+0100 (BST)>
+     *
+     * 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();
+      }
+    }
+  };
+});
+