html5 boiler plate
[scannr.git] / js / flotr2 / js / plugins / spreadsheet.js
blob:a/js/flotr2/js/plugins/spreadsheet.js -> blob:b/js/flotr2/js/plugins/spreadsheet.js
  /** Spreadsheet **/
  (function() {
   
  function getRowLabel(value){
  if (this.options.spreadsheet.tickFormatter){
  //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
  return this.options.spreadsheet.tickFormatter(value);
  }
  else {
  var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
  if (t) {
  return t.label;
  }
  return value;
  }
  }
   
  var
  D = Flotr.DOM,
  _ = Flotr._;
   
  Flotr.addPlugin('spreadsheet', {
  options: {
  show: false, // => show the data grid using two tabs
  tabGraphLabel: 'Graph',
  tabDataLabel: 'Data',
  toolbarDownload: 'Download CSV', // @todo: add better language support
  toolbarSelectAll: 'Select all',
  csvFileSeparator: ',',
  decimalSeparator: '.',
  tickFormatter: null,
  initialTab: 'graph'
  },
  /**
  * Builds the tabs in the DOM
  */
  callbacks: {
  'flotr:afterconstruct': function(){
  // @TODO necessary?
  //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');
   
  if (!this.options.spreadsheet.show) return;
   
  var ss = this.spreadsheet,
  container = D.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),
  graph = D.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+'</div>'),
  data = D.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+'</div>'),
  offset;
   
  ss.tabsContainer = container;
  ss.tabs = { graph : graph, data : data };
   
  D.insert(container, graph);
  D.insert(container, data);
  D.insert(this.el, container);
   
  offset = D.size(data).height + 2;
  this.plotOffset.bottom += offset;
   
  D.setStyles(container, {top: this.canvasHeight-offset+'px'});
   
  this.
  observe(graph, 'click', function(){ss.showTab('graph');}).
  observe(data, 'click', function(){ss.showTab('data');});
  if (this.options.spreadsheet.initialTab !== 'graph'){
  ss.showTab(this.options.spreadsheet.initialTab);
  }
  }
  },
  /**
  * Builds a matrix of the data to make the correspondance between the x values and the y values :
  * X value => Y values from the axes
  * @return {Array} The data grid
  */
  loadDataGrid: function(){
  if (this.seriesData) return this.seriesData;
   
  var s = this.series,
  rows = {};
   
  /* The data grid is a 2 dimensions array. There is a row for each X value.
  * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
  **/
  _.each(s, function(serie, i){
  _.each(serie.data, function (v) {
  var x = v[0],
  y = v[1],
  r = rows[x];
  if (r) {
  r[i+1] = y;
  } else {
  var newRow = [];
  newRow[0] = x;
  newRow[i+1] = y;
  rows[x] = newRow;
  }
  });
  });
   
  // The data grid is sorted by x value
  this.seriesData = _.sortBy(rows, function(row, x){
  return parseInt(x, 10);
  });
  return this.seriesData;
  },
  /**
  * Constructs the data table for the spreadsheet
  * @todo make a spreadsheet manager (Flotr.Spreadsheet)
  * @return {Element} The resulting table element
  */
  constructDataGrid: function(){
  // If the data grid has already been built, nothing to do here
  if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;
   
  var s = this.series,
  datagrid = this.spreadsheet.loadDataGrid(),
  colgroup = ['<colgroup><col />'],
  buttonDownload, buttonSelect, t;
   
  // First row : series' labels
  var html = ['<table class="flotr-datagrid"><tr class="first-row">'];
  html.push('<th>&nbsp;</th>');
  _.each(s, function(serie,i){
  html.push('<th scope="col">'+(serie.label || String.fromCharCode(65+i))+'</th>');
  colgroup.push('<col />');
  });
  html.push('</tr>');
  // Data rows
  _.each(datagrid, function(row){
  html.push('<tr>');
  _.times(s.length+1, function(i){
  var tag = 'td',
  value = row[i],
  // TODO: do we really want to handle problems with floating point
  // precision here?
  content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
  if (i === 0) {
  tag = 'th';
  var label = getRowLabel.call(this, content);
  if (label) content = label;
  }
   
  html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');
  }, this);
  html.push('</tr>');
  }, this);
  colgroup.push('</colgroup>');
  t = D.node(html.join(''));
   
  /**
  * @TODO disabled this
  if (!Flotr.isIE || Flotr.isIE == 9) {
  function handleMouseout(){
  t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
  }
  function handleMouseover(e){
  var td = e.element(),
  siblings = td.previousSiblings();
  t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
  t.select('colgroup col')[siblings.length].addClassName('hover');
  }
  _.each(t.select('td'), function(td) {
  Flotr.EventAdapter.
  observe(td, 'mouseover', handleMouseover).
  observe(td, 'mouseout', handleMouseout);
  });
  }
  */
   
  buttonDownload = D.node(
  '<button type="button" class="flotr-datagrid-toolbar-button">' +
  this.options.spreadsheet.toolbarDownload +
  '</button>');
   
  buttonSelect = D.node(
  '<button type="button" class="flotr-datagrid-toolbar-button">' +
  this.options.spreadsheet.toolbarSelectAll+
  '</button>');
   
  this.
  observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
  observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));
   
  var toolbar = D.node('<div class="flotr-datagrid-toolbar"></div>');
  D.insert(toolbar, buttonDownload);
  D.insert(toolbar, buttonSelect);
   
  var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
  container = D.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+
  this.canvasWidth+'px;height:'+containerHeight+'px;overflow:auto;z-index:10"></div>');
   
  D.insert(container, toolbar);
  D.insert(container, t);
  D.insert(this.el, container);
  this.spreadsheet.datagrid = t;
  this.spreadsheet.container = container;
   
  return t;
  },
  /**
  * Shows the specified tab, by its name
  * @todo make a tab manager (Flotr.Tabs)
  * @param {String} tabName - The tab name
  */
  showTab: function(tabName){
  if (this.spreadsheet.activeTab === tabName){
  return;
  }
  switch(tabName) {
  case 'graph':
  D.hide(this.spreadsheet.container);
  D.removeClass(this.spreadsheet.tabs.data, 'selected');
  D.addClass(this.spreadsheet.tabs.graph, 'selected');
  break;
  case 'data':
  if (!this.spreadsheet.datagrid)
  this.spreadsheet.constructDataGrid();
  D.show(this.spreadsheet.container);
  D.addClass(this.spreadsheet.tabs.data, 'selected');
  D.removeClass(this.spreadsheet.tabs.graph, 'selected');
  break;
  default:
  throw 'Illegal tab name: ' + tabName;
  }
  this.spreadsheet.activeTab = tabName;
  },
  /**
  * Selects the data table in the DOM for copy/paste
  */
  selectAllData: function(){
  if (this.spreadsheet.tabs) {
  var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();
   
  this.spreadsheet.showTab('data');
   
  // deferred to be able to select the table
  setTimeout(function () {
  if ((doc = node.ownerDocument) && (win = doc.defaultView) &&
  win.getSelection && doc.createRange &&
  (selection = window.getSelection()) &&
  selection.removeAllRanges) {
  range = doc.createRange();
  range.selectNode(node);
  selection.removeAllRanges();
  selection.addRange(range);
  }
  else if (document.body && document.body.createTextRange &&
  (range = document.body.createTextRange())) {
  range.moveToElementText(node);
  range.select();
  }
  }, 0);
  return true;
  }
  else return false;
  },
  /**
  * Converts the data into CSV in order to download a file
  */
  downloadCSV: function(){
  var csv = '',
  series = this.series,
  options = this.options,
  dg = this.spreadsheet.loadDataGrid(),
  separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);
   
  if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
  throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
  }
   
  // The first row
  _.each(series, function(serie, i){
  csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
  });
   
  csv += "%0D%0A"; // \r\n
   
  // For each row
  csv += _.reduce(dg, function(memo, row){
  var rowLabel = getRowLabel.call(this, row[0]) || '';
  rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
  var numbers = row.slice(1).join(separator);
  if (options.spreadsheet.decimalSeparator !== '.') {
  numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
  }
  return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
  }, '', this);
   
  if (Flotr.isIE && Flotr.isIE < 9) {
  csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
  window.open().document.write(csv);
  }
  else window.open('data:text/csv,'+csv);
  }
  });
  })();