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