      1 define([
      2     'jquery',
      3     'underscore',
      4     'view',
      5     'viewcontroller',
      6     'd3',
      7     'contextmenu',
      8     'filesaver'
      9 ], function($, _, DecompositionView, ViewControllers, d3, contextmenu,
     10             FileSaver) {
     11   var EmperorViewController = ViewControllers.EmperorViewController;
     12
     13   /**
     14    * @class AxesController
     15    *
     16    * Controls the axes that are displayed on screen as well as their
     17    * orientation.
     18    *
     19    * @param {UIState} uiState The shared state
     20    * @param {Node} container Container node to create the controller in.
     21    * @param {Object} decompViewDict This is object is keyed by unique
     22    * identifiers and the values are DecompositionView objects referring to a
     23    * set of objects presented on screen. This dictionary will usually be shared
     24    * by all the tabs in the application. This argument is passed by reference.
     25    *
     26    * @return {AxesController}
     27    * @constructs AxesController
     28    * @extends EmperorViewController
     29    */
     30   function AxesController(uiState, container, decompViewDict) {
     31     var helpmenu = 'Change the visible dimensions of the data';
     32     var title = 'Axes';
     33     var scope = this;
     34
     35     EmperorViewController.call(this, uiState, container, title, helpmenu,
     36                                decompViewDict);
     37
     38     this.$viewTypeDiv = $('<div name="emperor-viewtype-div"></div>');
     39     this.$viewTypeDiv.css({
     40         'margin': '0 auto',
     41         'width': '100%',
     42         'height': '100%'
     43     });
     44     this.$viewTypeDiv.attr('title', 'Change the selected View Type');
     45
     46     var radioName = 'emperor.viewType_' + this.identifier;
     47     if (this.UIState['view.viewType'] === 'scatter') {
     48
     49
     50       this.$radioScatter = $('<input type="radio" name="' + radioName + '" ' +
     51         'value="scatter" checked> Scatter </input>');
     52     }
     53     else {
     54       this.$radioScatter = $('<input type="radio" name="' + radioName + '" ' +
     55         'value="scatter"> Scatter </input>');
     56     }
     57
     58     if (this.UIState['view.viewType'] === 'parallel-plot') {
     59       this.$radioParallelPlot = $(
     60         '<input type="radio" name="' + radioName + '" ' +
     61         'value="parallel-plot" checked> Parallel Plot </input>');
     62     }
     63     else {
     64       this.$radioParallelPlot = $(
     65         '<input type="radio" name="' + radioName + '" ' +
     66         'value="parallel-plot"> Parallel Plot </input>');
     67     }
     68
     69     this.$viewTypeDiv.append(this.$radioScatter);
     70     this.$viewTypeDiv.append(this.$radioParallelPlot);
     71
     72     this.$radioScatter.change(function() {
     73       scope.UIState.setProperty('view.viewType', 'scatter');
     74     });
     75
     76     this.$radioParallelPlot.change(function() {
     77       scope.UIState.setProperty('view.viewType', 'parallel-plot');
     78     });
     79
     80     this.$header.prepend($('<hr>'));
     81     this.$header.prepend(this.$viewTypeDiv);
     82
     83     var colors = '<table style="width:inherit; border:none;" title="">';
     84     colors += '<tr><td>Axes and Labels Color</td>';
     85     colors += '<td><input type="text" name="axes-color"/></td></tr>';
     86     colors += '<tr><td>Background Color</td>';
     87     colors += '<td><input type="text" name="background-color"/></td>';
     88     colors += this._procrustesControllers();
     89     colors += '</table>';
     90
     91     this.$body.append(colors);
     92
     93     // the jupyter notebook adds style on the tables, so remove it
     94     this.$body.find('tr').css('border', 'none');
     95     this.$body.find('td').css('border', 'none');
     96
     97     var opts = {color: 'white',
     98                 preferredFormat: 'name',
     99                 palette: [['black', 'white']],
    100                 showPalette: true,
    101                 showInput: true,
    102                 allowEmpty: true,
    103                 showInitial: true,
    104                 clickoutFiresChange: true,
    105                 hideAfterPaletteSelect: true,
    106                 change: function(color) {
    107                   // null means hide axes and labels
    108                   if (color !== null) {
    109                     // We let the controller deal with the callback, the only
    110                     // things we need are the name of the element triggering
    111                     // the color change and the color
    112                     color = color.toHexString();
    113                   }
    114                   scope._colorChanged($(this).attr('name'), color);
    115                 }
    116     };
    117
    118
    119     // Don't propagate the keydown and keypress events so that inputing a color
    120     // doesn't interfere with the shortcuts of the Jupyter Notebook
    121     var stop = function(event) {
    122       event.stopPropagation();
    123     };
    124
    125     // spectrumify all the elements in the body that have a name ending in
    126     // color
    127     this._$axesColor = this.$body.find('[name="axes-color"]');
    128     this._$axesColor
    129       .spectrum(opts)
    130       .spectrum('container')
    131       .find('.sp-input')
    132       .on('keydown keypress', stop);
    133
    134     opts.color = 'black';
    135     opts.allowEmpty = false;
    136     this._$backgroundColor = this.$body.find('[name="background-color"]');
    137     this._$backgroundColor
    138       .spectrum(opts)
    139       .spectrum('container')
    140       .find('.sp-input')
    141       .on('keydown keypress', stop);
    142
    143     // these initializations will be ignored if there are no edges in the views
    144     opts.color = 'white';
    145     opts.showPalette = false;
    146     this._$referenceEdgeColor = this.$body.find(
    147       '[name="reference-edge-color"]');
    148     this._$referenceEdgeColor
    149       .spectrum(opts)
    150       .spectrum('container')
    151       .find('.sp-input')
    152       .on('keydown keypress', stop);
    153
    154     opts.color = 'red';
    155     this._$otherEdgeColor = this.$body.find('[name="other-edge-color"]');
    156     this._$otherEdgeColor
    157       .spectrum(opts)
    158       .spectrum('container')
    159       .find('.sp-input')
    160       .on('keydown keypress', stop);
    161
    162     /**
    163      * @type {Node}
    164      * jQuery object containing the scree plot.
    165      *
    166      * The style set here is important, allows for automatic resizing.
    167      *
    168      * @private
    169      */
    170     this.$_screePlotContainer = $('<div name="scree-plot">');
    171     this.$_screePlotContainer.attr('title', '');
    172     this.$_screePlotContainer.css({'display': 'inline-block',
    173                                    'position': 'relative',
    174                                    'width': '100%',
    175                                    'padding-bottom': '100%',
    176                                    'vertical-align': 'middle',
    177                                    'overflow': 'hidden'});
    178
    179     this.$body.append(this.$_screePlotContainer);
    180
    181     /**
    182      * @type {Node}
    183      * jQuery object containing the download scree plot button
    184      *
    185      * See also the private method _downloadScreePlot
    186      */
    187     this.$saveButton = $('<button> </button>');
    188     this.$saveButton.css({
    189       'position': 'absolute',
    190       'z-index': '3',
    191       'top': '10px',
    192       'right': '5px'
    193     }).button({
    194       text: false, icons: {primary: ' ui-icon-circle-arrow-s'}
    195     }).attr('title', 'Download Scree Plot');
    196     this.$_screePlotContainer.append(this.$saveButton);
    197
    198     /**
    199      * @type {Node}
    200      * The SVG node where the scree plot lives. For use with D3.
    201      */
    202     this.svg = null;
    203
    204     /**
    205      * @type {Node}
    206      * The display table where information about currently visible axes is
    207      * shown.
    208      */
    209     this.$table = null;
    210
    211     /**
    212      * @type {Bool[]}
    213      * Which axes are 'flipped', by default all are set to false.
    214      * @private
    215      */
    216     this._flippedAxes = [false, false, false];
    217
    218     // initialize interface elements here
    219     $(this).ready(function() {
    220       scope.buildDisplayTable();
    221       scope._buildScreePlot();
    222
    223       if (scope.ready !== null) {
    224         scope.ready();
    225       }
    226     });
    227
    228     return this;
    229   }
    230   AxesController.prototype = Object.create(EmperorViewController.prototype);
    231   AxesController.prototype.constructor = EmperorViewController;
    232
    233   /**
    234    * Create a table to display the visible axis information.
    235    *
    236    * Note that when this method is executed the table is destroyed, if it
    237    * exists, and recreated with the appropriate information.
    238    *
    239    */
    240   AxesController.prototype.buildDisplayTable = function() {
    241     if (this.$table !== null) {
    242       this.$table.remove();
    243     }
    244
    245     if (this.UIState['view.viewType'] === 'parallel-plot') {
    246     // Disables axes choices, not used for parallel-plot.
    247       return;
    248     }
    249
    250     var view = this.getView(), scope = this;
    251
    252     var $table = $('<table></table>'), $row, $td, widgets;
    253     var names = ['First', 'Second', 'Third'];
    254
    255     $table.attr('title', 'Modify the axes visible on screen');
    256     $table.css({'border': 'none',
    257                 'width': 'inherit',
    258                 'text-align': 'left',
    259                 'padding-bottom': '10%'});
    260
    261     $table.append('<tr><th>Axis</th><th>Visible</th><th>Invert</th></tr>');
    262
    263     _.each(view.visibleDimensions, function(dimension, index) {
    264       widgets = scope._makeDimensionWidgets(index);
    265
    266       $row = $('<tr></tr>');
    267
    268       // axis name
    269       $row.append('<td>' + names[index] + '</td>');
    270
    271       // visible dimension menu
    272       $td = $('<td></td>');
    273       // this acts as the minimum width of the column
    274       $td.css('width', '100px');
    275       $td.append(widgets.menu);
    276       $row.append($td);
    277
    278       // inverted checkbox
    279       $td = $('<td></td>');
    280       $td.append(widgets.checkbox);
    281       $row.append($td);
    282
    283       $table.append($row);
    284     });
    285
    286     this.$table = $table;
    287     this.$header.append(this.$table);
    288
    289     // the jupyter notebook adds style on the tables, so remove it
    290     this.$header.find('tr').css('border', 'none');
    291     this.$header.find('td').css('border', 'none');
    292   };
    293
    294   /**
    295    * Method to create dropdown menus and checkboxes
    296    *
    297    * @param {Integer} position The position of the axis for which the widgets
    298    * are being created.
    299    *
    300    * @private
    301    */
    302   AxesController.prototype._makeDimensionWidgets = function(position) {
    303     if (position > 2 || position < 0) {
    304       throw Error('Cannot create widgets for position: ' + position);
    305     }
    306
    307     var scope = this, $check, $menu;
    308     var decomposition = scope.getView().decomp;
    309     var visibleDimension = scope.getView().visibleDimensions[position];
    310
    311     $menu = $('<select>');
    312     $menu.css({'width': '100%'});
    313     $check = $('<input type="checkbox">');
    314
    315     // if the axis is flipped, then show the checkmark
    316     $check.prop('checked', scope._flippedAxes[position]);
    317
    318     _.each(decomposition.axesNames, function(name, index) {
    319       $menu.append($('<option>').attr('value', name).text(name));
    320     });
    321
    322     if (position === 2) {
    323       $menu.append($('<option>').attr('value', null)
    324                                 .text('Hide Axis (make 2D)'));
    325     }
    326
    327     $menu.on('change', function() {
    328       var index = $(this).prop('selectedIndex');
    329
    330       // the last element is the "hide" option, only for the third menu, if
    331       // that's the case the selected index becomes null so it can be hidden
    332       if (position === 2 && index === decomposition.dimensions) {
    333         index = null;
    334       }
    335
    336       scope.updateVisibleAxes(index, position);
    337     });
    338
    339     $check.on('change', function() {
    340       scope.flipAxis(visibleDimension);
    341     });
    342
    343     $(function() {
    344       // if the selected index is null, it means we need to select the last
    345       // element in the dropdown menu
    346       var idx = visibleDimension;
    347       if (idx === null) {
    348         idx = decomposition.dimensions;
    349
    350         // disable the flip axes checkbox
    351         $check.attr('disabled', true);
    352       }
    353       $menu.prop('selectedIndex', idx);
    354     });
    355
    356     return {menu: $menu, checkbox: $check};
    357   };
    358
    359   /**
    360    * Method to build the scree plot and updates the interface appropriately.
    361    *
    362    * @private
    363    *
    364    */
    365   AxesController.prototype._buildScreePlot = function() {
    366     var scope = this;
    367     var percents = this.getView().decomp.percExpl;
    368     var names = this.getView().decomp.axesNames;
    369     percents = _.map(percents, function(val, index) {
    370       // +1 to account for zero-indexing
    371       return {'axis': names[index] + ' ', 'percent': val,
    372               'dimension-index': index};
    373     });
    374
    375     // this chart is based on the example hosted in
    376     // https://bl.ocks.org/mbostock/3885304
    377     var margin = {top: 10, right: 10, bottom: 30, left: 40},
    378         width = this.$body.width() - margin.left - margin.right,
    379         height = (this.$body.height() * 0.40) - margin.top - margin.bottom;
    380
    381     var tooltip = d3.select('body').append('div').style({
    382       'position': 'absolute',
    383       'display': 'none',
    384       'color': 'black',
    385       'height': 'auto',
    386       'text-align': 'center',
    387       'background-color': 'rgba(200,200,200,0.5)',
    388       'border-radius': '5px',
    389       'cursor': 'default',
    390       'font-family': 'Helvetica, sans-serif',
    391       'font-size': '14px'
    392     }).html('Percent Explained');
    393
    394     var x = d3.scale.ordinal()
    395       .rangeRoundBands([0, width], 0.1);
    396
    397     var y = d3.scale.linear()
    398       .range([height, 0]);
    399
    400     var xAxis = d3.svg.axis()
    401       .scale(x)
    402       .orient('bottom');
    403
    404     var yAxis = d3.svg.axis()
    405       .scale(y)
    406       .orient('left')
    407       .ticks(4);
    408
    409     // the container of the scree plot
    410     var svg = d3.select(this.$_screePlotContainer.get(0)).append('svg')
    411       .attr('preserveAspectRatio', 'xMinYMin meet')
    412       .attr('viewBox', (-margin.left) + ' ' +
    413                        (-margin.top) + ' ' +
    414                        (width + margin.left + margin.right) + ' ' +
    415                        (height + margin.top + margin.bottom))
    416       .style('display', 'inline-block')
    417       .style('position', 'absolute')
    418       .style('left', '0')
    419       .style('top', '0')
    420       .append('g');
    421
    422     this.$_screePlotContainer.height(height + margin.top + margin.bottom);
    423
    424     // Only keep dimensions resulting of an ordination i.e. with a positive
    425     // percentage explained.
    426     percents = percents.filter(function(x) { return x.percent >= 0; });
    427
    428     // creation of the chart itself
    429     x.domain(percents.map(function(d) { return d.axis; }));
    430     y.domain([0, d3.max(percents, function(d) { return d.percent; })]);
    431
    432     // create the x axis
    433     svg.append('g')
    434       .attr('font', '10px sans-serif')
    435       .attr('transform', 'translate(0,' + height + ')')
    436       .call(xAxis);
    437
    438     // create the y axis
    439     svg.append('g')
    440       .attr('font', '10px sans-serif')
    441       .call(yAxis)
    442       .append('text')
    443       .attr('transform', 'translate(' + (margin.left * (-0.8)) +
    444                          ',' + height / 2 + ') rotate(-90)')
    445       .style('text-anchor', 'middle')
    446       .text('% Variation Explained');
    447
    448     // draw the bars in the chart
    449     svg.selectAll('.bar')
    450       .data(percents)
    451       .enter().append('rect')
    452       .attr('dimension-index', function(d) { return d['dimension-index']; })
    453       .attr('fill', 'steelblue')
    454       .attr('x', function(d) { return x(d.axis); })
    455       .attr('width', x.rangeBand())
    456       .attr('y', function(d) { return y(d.percent); })
    457       .attr('height', function(d) { return height - y(d.percent); })
    458       .on('mousemove', function(d) {
    459         // midpoint: set the midpoint to zero in case something is off
    460         // offset: avoid some flickering
    461         var midpoint = (parseFloat(tooltip.style('width')) / 2) || 0,
    462             offset = 25;
    463
    464         tooltip.html(d.percent.toFixed(2));
    465
    466         tooltip.style({
    467           'left': d3.event.pageX - midpoint + 'px',
    468           'top': d3.event.pageY - offset + 'px'
    469         });
    470
    471         // after positioning the tooltip display the view, otherwise weird
    472         // resizing glitches occur
    473         tooltip.style({'display': 'inline-block'});
    474       })
    475       .on('mouseout', function(d) {
    476         tooltip.style('display', 'none');
    477       });
    478
    479     // figure title
    480     svg.append('text')
    481       .attr('x', (width / 2))
    482       .attr('y', 0)
    483       .attr('text-anchor', 'middle')
    484       .text('Scree Plot');
    485
    486     // set the style for the axes lines and ticks
    487     svg.selectAll('axis,path,line')
    488       .style('fill', 'none')
    489       .style('stroke', 'black')
    490       .style('stroke-width', '2')
    491       .style('shape-rendering', 'crispEdges');
    492
    493     this.screePlot = svg;
    494
    495     this.$saveButton.on('click', function() {
    496       scope._downloadScreePlot();
    497     });
    498   };
    499
    500   /**
    501    *
    502    * Helper method to download the scree plot as an SVG file.
    503    *
    504    */
    505   AxesController.prototype._downloadScreePlot = function() {
    506       // converting svgRenderer to string: http://stackoverflow.com/a/17415624
    507       var XMLS = new XMLSerializer();
    508       var svg = XMLS.serializeToString(this.screePlot.node().ownerSVGElement);
    509
    510       blob = new Blob([svg], {type: 'image/svg+xml'});
    511       saveAs(blob, 'emperor-scree-plot.svg');
    512   };
    513
    514   /**
    515    *
    516    * Helper method to optionally create the procrustes controllers
    517    *
    518    */
    519   AxesController.prototype._procrustesControllers = function() {
    520     var out = '';
    521     var shouldDraw = _.values(this.decompViewDict).some(function(view) {
    522       return view.decomp.edges.length > 0;
    523     });
    524
    525     // if we have at least one decomposition with edges then we add the
    526     // controllers.
    527     if (shouldDraw) {
    528       out += '<tr><td> </td></tr>';
    529       out += '<tr>';
    530       out += '<td>Edge Color (reference)</td>';
    531       out += '<td><input type="text" name="reference-edge-color"/></td>';
    532       out += '</tr>';
    533       out += '<tr>';
    534       out += '<td>Edge Color (other)</td>';
    535       out += '<td><input type="text" name="other-edge-color"/></td>';
    536       out += '</tr>';
    537     }
    538
    539     return out;
    540   };
    541
    542   /**
    543    *
    544    * Get the reference edge color from the UI picker.
    545    *
    546    */
    547   AxesController.prototype.getReferenceEdgeColor = function() {
    548     if (this._$referenceEdgeColor.length === 0) {
    549       return null;
    550     }
    551
    552     return this._$referenceEdgeColor.spectrum('get').toHexString();
    553   };
    554
    555   /**
    556    *
    557    * Get the other edge color from the UI picker.
    558    *
    559    */
    560   AxesController.prototype.getOtherEdgeColor = function() {
    561     if (this._$otherEdgeColor.length === 0) {
    562       return null;
    563     }
    564
    565     return this._$otherEdgeColor.spectrum('get').toHexString();
    566   };
    567
    568   /**
    569    *
    570    * Get the background color from the UI picker.
    571    *
    572    */
    573   AxesController.prototype.getBackgroundColor = function() {
    574     return this._$backgroundColor.spectrum('get').toHexString();
    575   };
    576
    577   /**
    578    *
    579    * Get the axes color from the UI picker.
    580    *
    581    */
    582   AxesController.prototype.getAxesColor = function() {
    583     return this._$axesColor.spectrum('get').toHexString();
    584   };
    585
    586   /**
    587    *
    588    * Set the reference edge color (to the UI and the underlying models).
    589    *
    590    * @param {string} color The color to set, in a CSS 6-digit hex format i.e.
    591    * #ff0000 for red
    592    *
    593    */
    594   AxesController.prototype.setReferenceEdgeColor = function(color) {
    595     if (this._$referenceEdgeColor.length) {
    596       this._$referenceEdgeColor.spectrum('set', color);
    597
    598       _.each(this.decompViewDict, function(decView) {
    599         decView.lines.left.material.color.set(color);
    600         decView.needsUpdate = true;
    601       });
    602     }
    603   };
    604
    605   /**
    606    *
    607    * Set the other edge color (to the UI and the underlying models).
    608    *
    609    * @param {string} color The color to set, in a CSS 6-digit hex format i.e.
    610    * #ff0000 for red
    611    *
    612    */
    613   AxesController.prototype.setOtherEdgeColor = function(color) {
    614     if (this._$otherEdgeColor.length) {
    615       this._$otherEdgeColor.spectrum('set', color);
    616
    617       _.each(this.decompViewDict, function(decView) {
    618         decView.lines.right.material.color.set(color);
    619         decView.needsUpdate = true;
    620       });
    621     }
    622   };
    623
    624   /**
    625    *
    626    * Set the background color (to the UI and the underlying models).
    627    *
    628    * @param {string} color The color to set, in a CSS 6-digit hex format i.e.
    629    * #ff0000 for red
    630    *
    631    */
    632   AxesController.prototype.setBackgroundColor = function(color) {
    633     this._$backgroundColor.spectrum('set', color);
    634
    635     _.each(this.decompViewDict, function(decView) {
    636       decView.backgroundColor = color;
    637       decView.needsUpdate = true;
    638     });
    639   };
    640
    641   /**
    642    *
    643    * Set the axes color (to the UI and the underlying models).
    644    *
    645    * @param {string} color The color to set, in a CSS 6-digit hex format i.e.
    646    * #ff0000 for red
    647    *
    648    */
    649   AxesController.prototype.setAxesColor = function(color) {
    650     this._$axesColor.spectrum('set', color);
    651
    652     _.each(this.decompViewDict, function(decView) {
    653       decView.axesColor = color;
    654       decView.needsUpdate = true;
    655     });
    656   };
    657
    658   /**
    659    * Callback to reposition an axis
    660    *
    661    * @param {Integer} index The index of the dimension to set as a new visible
    662    * axis, in the corresponding position indicated by `position`.
    663    * @param {Integer} position The position where the new axis will be set.
    664    */
    665   AxesController.prototype.updateVisibleAxes = function(index, position) {
    666     // update all the visible dimensions
    667     _.each(this.decompViewDict, function(decView, key) {
    668       // clone to avoid indirectly modifying by reference
    669       var visibleDimensions = _.clone(decView.visibleDimensions);
    670
    671       visibleDimensions[position] = index;
    672       decView.changeVisibleDimensions(visibleDimensions);
    673     });
    674
    675     this._flippedAxes[position] = false;
    676
    677     this.buildDisplayTable();
    678   };
    679
    680   /**
    681    * Callback to change the orientation of an axis
    682    *
    683    * @param {Integer} index The index of the dimension to re-orient, note that
    684    * if this index is not visible, this callback will take no effect.
    685    */
    686   AxesController.prototype.flipAxis = function(index) {
    687     var axIndex;
    688
    689     // update all the visible dimensions
    690     _.each(this.decompViewDict, function(decView, key) {
    691
    692       axIndex = decView.visibleDimensions.indexOf(index);
    693
    694       if (axIndex !== -1) {
    695         decView.flipVisibleDimension(index);
    696       }
    697     });
    698
    699     // needs to cast to boolean, because XOR returns an integer
    700     this._flippedAxes[axIndex] = Boolean(true ^ this._flippedAxes[axIndex]);
    701     this.buildDisplayTable();
    702   };
    703
    704   /**
    705    * Convenience to change color of the axes or the background
    706    *
    707    * @param {String} name The name of the element to change, it can be either
    708    * 'axes-color' or 'background-color'. If the plot displays procrustes data
    709    * then it can also accept 'reference-edge-color' and 'other-edge-color'.
    710    * @param {String} color The color to set to the `name`. Should be in a CSS
    711    * compatible format.
    712    *
    713    * @private
    714    */
    715   AxesController.prototype._colorChanged = function(name, color) {
    716     // for both cases update all the decomposition views and then set the
    717     // appropriate colors
    718     if (name === 'axes-color') {
    719       this.setAxesColor(color);
    720     }
    721     else if (name === 'background-color') {
    722       this.setBackgroundColor(color);
    723     }
    724     else if (name === 'reference-edge-color') {
    725       this.setReferenceEdgeColor(color);
    726     }
    727     else if (name === 'other-edge-color') {
    728       this.setOtherEdgeColor(color);
    729     }
    730     else {
    731       throw Error('Could not change color for element: "' + name + '"');
    732     }
    733   };
    734
    735   /**
    736    * Converts the current instance into a JSON string.
    737    *
    738    * @return {Object} JSON ready representation of self.
    739    */
    740   AxesController.prototype.toJSON = function() {
    741     var json = {};
    742
    743     var decView = this.getView();
    744
    745     json.visibleDimensions = decView.visibleDimensions;
    746     json.flippedAxes = this._flippedAxes;
    747
    748     json.backgroundColor = this.getBackgroundColor();
    749     json.axesColor = this.getAxesColor();
    750
    751     json.referenceEdgeColor = this.getReferenceEdgeColor();
    752     json.otherEdgeColor = this.getOtherEdgeColor();
    753
    754     //Save the viewType
    755     json.viewType = this.UIState['view.viewType'];
    756
    757     return json;
    758   };
    759
    760   /**
    761    * Decodes JSON string and modifies its own instance variables accordingly.
    762    *
    763    * @param {Object} Parsed JSON string representation of self.
    764    */
    765   AxesController.prototype.fromJSON = function(json) {
    766     var decView = this.getView(), scope = this;
    767
    768     decView.changeVisibleDimensions(json.visibleDimensions);
    769
    770     _.each(json.flippedAxes, function(element, index) {
    771       // if the values are different, the axes need to be inverted
    772       if (element !== scope._flippedAxes[index]) {
    773         scope.flipAxis(decView.visibleDimensions[index]);
    774       }
    775     });
    776
    777     // only set these colors if they are present, note that colors
    778     // are saved as
    779     if (json.axesColor !== undefined) {
    780       this.setAxesColor(json.axesColor);
    781     }
    782
    783     if (json.backgroundColor !== undefined) {
    784       this.setBackgroundColor(json.backgroundColor);
    785     }
    786
    787     // if procrustes information is available
    788     if (json.referenceEdgeColor !== undefined) {
    789       this.setReferenceEdgeColor(json.referenceEdgeColor);
    790     }
    791     if (json.otherEdgeColor !== undefined) {
    792       this.setOtherEdgeColor(json.otherEdgeColor);
    793     }
    794
    795     // make sure everything is up to date in the UI
    796     this.buildDisplayTable();
    797
    798     //Restore the viewType
    799     if (json.viewType !== undefined) {
    800       this.UIState.setProperty('view.viewType', json.viewType);
    801     }
    802   };
    803
    804   return AxesController;
    805 });
    806