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