this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; // Views module following classic module pattern (function($, my) { // Graph view for a Dataset using Flot graphing library. // // Initialization arguments: // // * model: recline.Model.Dataset // * config: (optional) graph configuration hash of form: // // { // group: {column name for x-axis}, // series: [{column name for series A}, {column name series B}, ... ], // graphType: 'line' // } // // NB: should *not* provide an el argument to the view but must let the view // generate the element itself (you can then append view.el to the DOM. my.FlotGraph = Backbone.View.extend({ tagName: "div", className: "data-graph-container", template: ' \
\
\

Help »

\

To create a chart select a column (group) to use as the x-axis \ then another column (Series A) to plot against it.

\

You can add add \ additional series by clicking the "Add series" button

\
\
\
\ \
\ \
\ \
\ \
\
\
\ \
\ \
\
\
\
\
\ \
\ \
\
\
\ \ ', events: { 'change form select': 'onEditorSubmit' , 'click .editor-add': 'addSeries' , 'click .action-remove-series': 'removeSeries' , 'click .action-toggle-help': 'toggleHelp' }, initialize: function(options, config) { var self = this; this.el = $(this.el); _.bindAll(this, 'render', 'redraw'); // we need the model.headers to render properly this.model.bind('change', this.render); this.model.currentDocuments.bind('add', this.redraw); this.model.currentDocuments.bind('reset', this.redraw); this.chartConfig = _.extend({ group: null, series: [], graphType: 'line' }, config) this.render(); }, toTemplateJSON: function() { return this.model.toJSON(); }, render: function() { htmls = $.mustache(this.template, this.toTemplateJSON()); $(this.el).html(htmls); // now set a load of stuff up this.$graph = this.el.find('.panel.graph'); // for use later when adding additional series // could be simpler just to have a common template! this.$seriesClone = this.el.find('.editor-series').clone(); this._updateSeries(); return this; }, onEditorSubmit: function(e) { var select = this.el.find('.editor-group select'); this._getEditorData(); // update navigation // TODO: make this less invasive (e.g. preserve other keys in query string) window.location.hash = window.location.hash.split('?')[0] + '?graph=' + JSON.stringify(this.chartConfig); this.redraw(); }, redraw: function() { // There appear to be issues generating a Flot graph if either: // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with // // Uncaught Invalid dimensions for plot, width = 0, height = 0 // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]); if (!this.plot && (!areWeVisible || this.model.currentDocuments.length == 0)) { return } // create this.plot and cache it if (!this.plot) { // only lines for the present options = { id: 'line', name: 'Line Chart' }; this.plot = $.plot(this.$graph, this.createSeries(), options); } this.plot.setData(this.createSeries()); this.plot.resize(); this.plot.setupGrid(); this.plot.draw(); }, _getEditorData: function() { $editor = this var series = this.$series.map(function () { return $(this).val(); }); this.chartConfig.series = $.makeArray(series) this.chartConfig.group = this.el.find('.editor-group select').val(); }, createSeries: function () { var self = this; var series = []; if (this.chartConfig) { $.each(this.chartConfig.series, function (seriesIndex, field) { var points = []; $.each(self.model.currentDocuments.models, function (index, doc) { var x = doc.get(self.chartConfig.group); var y = doc.get(field); if (typeof x === 'string') { x = index; } points.push([x, y]); }); series.push({data: points, label: field}); }); } return series; }, // Public: Adds a new empty series select box to the editor. // // All but the first select box will have a remove button that allows them // to be removed. // // Returns itself. addSeries: function (e) { e.preventDefault(); var element = this.$seriesClone.clone(), label = element.find('label'), index = this.$series.length; this.el.find('.editor-series-group').append(element); this._updateSeries(); label.append(' [Remove]'); label.find('span').text(String.fromCharCode(this.$series.length + 64)); return this; }, // Public: Removes a series list item from the editor. // // Also updates the labels of the remaining series elements. removeSeries: function (e) { e.preventDefault(); var $el = $(e.target); $el.parent().parent().remove(); this._updateSeries(); this.$series.each(function (index) { if (index > 0) { var labelSpan = $(this).prev().find('span'); labelSpan.text(String.fromCharCode(index + 65)); } }); this.onEditorSubmit(); }, toggleHelp: function() { this.el.find('.editor-info').toggleClass('editor-hide-info'); }, // Private: Resets the series property to reference the select elements. // // Returns itself. _updateSeries: function () { this.$series = this.el.find('.editor-series select'); } }); })(jQuery, recline.View);