jiskattema/spot

View on GitHub
src/pages/analyze.js

Summary

Maintainability
D
1 day
Test Coverage
var app = require('ampersand-app');
var $ = require('jquery');
var PageView = require('./base');
var templates = require('../templates');
var WidgetFrameView = require('./analyze/widget-frame');
var FacetbarItemView = require('./analyze/facetbar-item');
var sortablejs = require('sortablejs');

var AnalyzeHelp = require('./help/analyze');

// NOTE: gridster does not work properly with require()
// workaround via browserify-shim (configured in package.json)
require('gridster');

function initializeCharts (view) {
  var gridster = view._widgetsGridster;

  // BUGFIX: can sometimes get called before gridster is fully initialized
  if (!gridster) {
    return;
  }

  var i;
  for (i = 0; i < gridster.$widgets.length; i++) {
    var chartView = $(gridster.$widgets[i]).data('spotWidgetFrameView')._subviews[0];
    chartView.model.updateConfiguration();

    if (chartView.model.isConfigured) {
      if (!chartView.model.filter.isInitialized) {
        if (chartView.isInitialized) {
          chartView.deinitChart(); // deininit charts that had a filter released
        }
        chartView.model.filter.initDataFilter();
      }
      if (chartView.isInitialized) {
        // noop
      } else {
        chartView.initChart();
      }
    } else {
      if (chartView.isInitialized) {
        chartView.deinitChart();
      }
      if (chartView.model.filter.isInitialized) {
        chartView.model.filter.releaseDataFilter();
      }
    }
  }
}

function deinitializeCharts (view) {
  var gridster = view._widgetsGridster;

  var i;
  for (i = 0; i < gridster.$widgets.length; i++) {
    var chartView = $(gridster.$widgets[i]).data('spotWidgetFrameView')._subviews[0];
    if (chartView.isInitialized) {
      chartView.deinitChart();
    }
    if (chartView.model.isConfigured) {
      chartView.model.filter.releaseDataFilter();
    }
  }
}

function updateCharts (view) {
  var gridster = view._widgetsGridster;

  var i;
  for (i = 0; i < gridster.$widgets.length; i++) {
    var chartView = $(gridster.$widgets[i]).data('spotWidgetFrameView')._subviews[0];
    if (chartView.isInitialized) {
      chartView.update();
    }
  }
}
/**
 * Add a widget to the analyze page for the given filter
 *
 * view {View}             Ampersand View instance of the analyze page
 * filter {Filter}         Spot filter instance to create the widget for
 * editModeHint {boolean}  Try to start plot in editMode (ie. accepts dnd of facets) [true] or in interaction mode (false)
 */
function addWidgetForFilter (view, filter, editModeHint) {
  var gridster = view._widgetsGridster;
  var row = filter.row || 1;
  var col = filter.col || 1;
  var sizeX = filter.size_x || 3;
  var sizeY = filter.size_y || 3;

  var el = gridster.add_widget('<div class="widgetOuterFrame"></div>', sizeX, sizeY, col, row);
  var frameView = new WidgetFrameView({
    model: filter
  });

  // render, and render content of widget frame
  view.renderSubview(frameView, el[0]);
  frameView.renderContent();

  // link element and view so we can:
  // a) on remove, get to the HTMLElement from the WidgetFrameView
  // b) on resize, get to the WidgetFrameView from the HTMLElement
  frameView.gridsterHook = el[0];
  $(el[0]).data('spotWidgetFrameView', frameView);

  // try to initialize and render possibly present data
  // only follow editModeHint when the widget is configured, default to true
  var chartView = frameView.widget;
  chartView.model.updateConfiguration();
  if (chartView.model.isConfigured) {
    if (!filter.isInitialized) {
      filter.initDataFilter();
    }
    if (!chartView.isInitialized) {
      chartView.initChart();
    }
    chartView.update();

    frameView.editMode = editModeHint;
  } else {
    // widget is not configured, ignore editModeHint
    // and always go to edit mode
    frameView.editMode = true;
  }

  filter.on('newData', function () {
    chartView.update();
  });
}

module.exports = PageView.extend({
  template: templates.analyze.page,
  session: {
    fullscreenMode: ['boolean', true, true]
  },
  initialize: function () {
    this.pageName = 'analyze';
    this.fullscreenMode = app.fullscreenMode;
    // this.helpTemplate = templates.help.analyze;
    this.helpSteps = AnalyzeHelp.steps;
    this.helpHints = AnalyzeHelp.hints;




    // // show existing dataset list
    // app.me.datasets.forEach(function (dataset, i) {
    //   if (dataset.isActive) {
    //     console.log('dataset: ', dataset);
    //     dataset.facets.forEach(function (facet, j) {
    //       console.log('facet: ', facet);
    //     });
    //   }
    // });




    app.on('refresh', function () {
      initializeCharts(this);
      app.me.dataview.getData();
    }, this);

    this.once('remove', function () {
      // remove callbacks for 'app#refresh'
      app.off('refresh');

      // remove callbacks for 'filter#newData'
      app.me.dataview.filters.forEach(function (filter) {
        filter.off('newData');
      });
    });

    if (app.me.dataview.datasetIds.length === 0) {
      app.message({
        text: 'No data to analyze, please upload and/or select some datasets',
        type: 'ok'
      });
    }
  },
  derived: {
    dataString: {
      deps: ['model.dataTotal', 'model.dataSelected'],
      fn: function () {
        var percentage;
        if (this.model.dataTotal > 0) {
          percentage = 100.0 * this.model.dataSelected / this.model.dataTotal;
        } else {
          percentage = 0;
        }
        return this.model.dataTotal +
          ' total, ' +
          this.model.dataSelected +
          ' selected (' +
          percentage.toPrecision(3) +
          '%)';
      }
    }
  },
  bindings: {
    'fullscreenMode': [
      { type: 'toggle', hook: 'chart-bar', invert: true },
      { type: 'toggle', hook: 'facet-bar', invert: true }
    ],
    'dataString': {
      type: 'text',
      hook: 'data-string'
    }
  },
  events: {
    'click #viewAll': 'viewAll',
    'click #fullscreenButton': 'toggleFullscreen',
    'click #resetFiltersButton': 'resetFilters',
    'click #saveSessionButton': 'saveSession',
    'click .widgetIcon': 'addChart'
  },
  saveSession: function () {
    app.saveCurrentSession();
  },
  addChart: function (ev) {
    // what icon was clicked?
    var target = ev.target || ev.srcElement;
    var id = target.id;

    var filter = this.model.filters.add({ chartType: id });
    addWidgetForFilter(this, filter, true);
  },
  toggleFullscreen: function () {
    app.fullscreenMode = !app.fullscreenMode;
    this.fullscreenMode = app.fullscreenMode;
  },
  resetFilters: function () {
    app.me.dataview.pause();
    app.me.dataview.filters.forEach(function (filter) {
      // undo drill downs
      while (filter.zoomHistory.length > 0) {
        filter.zoomOut();
      }
      // and clear possible selection
      filter.zoomOut();
    });
    app.me.dataview.play();
    app.me.dataview.getData();
    app.message({
      text: 'Reselected all data',
      type: 'ok'
    });
  },
  viewAll: function () {
    this._subviews.forEach(function (v) {
      if (v._values && v._values.hasOwnProperty('editMode')) {
        v.editMode = false;
      }
    });
  },
  render: function (opts) {
    this.renderWithTemplate(this);

    this.renderCollection(this.model.facets, FacetbarItemView, this.queryByHook('facet-bar-items'), {
      filter: function (m) {
        return m.isActive;
      }
    });

    return this;
  },
  renderContent: function () {
    var widgetNeedsData = false;

    var el = document.getElementById('facetBar');
    this._facetsSortable = sortablejs.create(el, {
      draggable: '.mdl-chip',
      dataIdAttr: 'data-id',
      sort: false,
      group: {
        name: 'facets',
        pull: 'clone',
        put: false
      },
      onStart: function (evt) {
        var item = evt.item;
        var facetId = item.getAttribute('data-id');
        var facet = app.me.dataview.facets.get(facetId);
        app.trigger('dragStart', facet.type);
      },
      onEnd: function (evt) {
        app.trigger('dragEnd');
      },
      onAdd: function (evt) {
        var item = evt.item;
        item.remove();
      }
    });
    this._widgetsGridster = $('[id~=widgets]').gridster({
      widget_base_dimensions: [100, 100],
      min_cols: 1,
      max_cols: 20,
      avoid_overlapped_widgets: false,
      widget_selector: 'div',
      draggable: {
        enabled: true,
        handle: '.widgetDragBar',
        stop: function () {
          var widgets = this.$widgets;
          var i = 0;
          for (i = 0; i < widgets.length; i++) { // $.each
            var widget = widgets[i];
            var data = $(widget).data();
            var filter = data['spotWidgetFrameView'].model.filter;
            var grid = data['coords'].grid;

            filter.row = grid.row;
            filter.col = grid.col;
            filter.size_x = grid.size_x;
            filter.size_y = grid.size_y;
          }
        }
      },
      resize: {
        enabled: true,
        start: function (e, ui, widget) {
          var view = widget.data('spotWidgetFrameView')._subviews[0];
          view.deinitChart();
        },
        stop: function (e, ui, widget) {
          var view = widget.data('spotWidgetFrameView')._subviews[0];
          var filter = view.model.filter;
          if (view.isInitialized) {
            view.update();
          }

          // keep track of the position of the chart
          var info = widget.data('coords').grid;
          filter.row = info.row;
          filter.col = info.col;
          filter.size_x = info.size_x;
          filter.size_y = info.size_y;
          if (view.model.isConfigured) {
            view.initChart();
          }
          if (view.isInitialized) {
            view.update();
          }
        }
      }
    }).data('gridster');

    this.on('remove', function () {
      this._facetsSortable.destroy();
      this._widgetsGridster.destroy();
    });

    // pause dataset to prevent needless data updates
    this.model.pause();

    // add widgets for each filter to the page
    this.model.filters.forEach(function (filter) {
      addWidgetForFilter(this, filter, false);

      if (!filter.data || filter.data.length === 0) {
        widgetNeedsData = true;
      }
    }, this);

    // done, unpause the dataset
    this.model.play();

    if (widgetNeedsData) {
      app.me.dataview.getData();
    }

    // do a last pass to render data
    updateCharts(this);
  },
  initializeCharts: function () {
    initializeCharts(this);
  },
  deinitializeCharts: function () {
    deinitializeCharts(this);
  },
  updateCharts: function () {
    updateCharts(this);
  }
});