Source: features/render/renderController.ts

import {MetricsPanelCtrl} from 'app/plugins/sdk';
import TimeSeries from 'app/core/time_series2';
import _ from 'lodash';
import * as d3 from '../../libs/d3/build/d3.js' ;

import { renderDefaults } from "./renderDefaults.js";
import { renderEditor } from "./renderEditor.js";

/**
 * @alias renderFeature
 * @classdesc <h2>render feature</h2>
 * Implementation for a feature.<br>
 * Makes use of the mediator pattern in order to subscribe the feature to
 * the plugin's event, through the $scope reference which is passed to it.
 * <br>
 * <br><h3>Functionaliy<h3><br>
 * This feature is responsible for representing data values in an svg, including <br>
 * the load of the svg in the DOM.
 * <i>Subscribed events</i>
 * <ul>
 *   <li>init-edit-mode</li>
 *   <li>panel-initialized</li>
 *   <li>render</li>
 * </ul>
 * @requires D3.js
 */
export default class Feature{
  /**
   * constructor - description <br>
   * Important the use of _.cloneDeep to ensure that no two instances of the same plugin
   * share references of the same variables.
   *
   * @param  {type} $scope A reference to the plugin's scope for the subscription to events
   * @return {type}        New instance of Feature
   */
  constructor( $scope){
      this.$scope = $scope;
      this.panelController = $scope.ctrl;
      this.panel = this.panelController.panel;

      const defaults = _.cloneDeep(renderDefaults)
      _.defaults( this.panelController.panel, defaults);

      this.panelController.events.on( 'init-edit-mode', this.onInitEditMode.bind(this));
      //this.panelController.events.on( 'data-received', this.onDataReceived);
      this.panelController.events.on( 'panel-initialized', this.onPanelInitialized.bind(this));
      this.panelController.events.on( 'render', this.onRender.bind(this));
      //this.panelController.events.on( 'refresh', this.onRefresh);
  }

  /**
   * onInitEditMode - Handler for the event : init-edit-mode<br>
   *
   * @memberof renderFeature
   */
  onInitEditMode(){
    this.panelController.addEditorTab( 'Render', renderEditor( this.$scope), 2);
  }

  /**
   * onRender - Handler for the event : render<br>
   * Requires of an element containing the svg to update based on the data
   * @memberof renderFeature
   */
  onRender(){
    this.renderSala( '#'+this.panel.panelDivId, this.panel.data);
  }

  /**
   * onPanelInitialized - Handler for the event : panel-initialized <br>
   * Renders the svg and data for the first time. Including :
   * <ol>
   * <li>Create a first instance of the color scale</li>
   * <li>Load and append it to the specified element by the panelDivId identyfier</li>
   * <li>Create an event for rendering the data over the svg</li>
   * </ol>
   *
   * @memberof renderFeature
   */
  onPanelInitialized(){
    this.actualizarColores();
    this.cargarPlano( this.panel.panelDivId, this.panel.render.baseMapRoute + this.panel.render.mapRoute + ".svg");
    this.panelController.render();
  }

  /**
   * actualizarColores - Changes the instance of color scale to be used by D3.<br><br>
   * For discrete representation (fixed number of colors) an ad-hoc function is done.<br>
   * For continous representation (range of colors) a D3.js scale function is used, based on <br>
   * domain and color selected.
   *
   * @memberof renderFeature
   */
  actualizarColores(){
    if(this.panel.render.discrete_continuous == true){
      this.scaleColor = (function( value){
        if( value <= this.panel.render.thresholds[0]){
          return( this.panel.render.colors[0]);
        }else if( value <= this.panel.render.thresholds[1]){
          return( this.panel.render.colors[1]);
        }else{
          return( this.panel.render.colors[2]);
        }
      });
    }else{
      this.scaleColor =  d3.scaleLinear()
        .domain( this.panel.render.domain)
        .range( this.panel.render.colors);
    }
  }

  /**
   * cargarPlano - Loads the svg resource into the DOM, hanging from the<br>
   * element specified by the elementIdentifyer id attribute.
   *
   * @param  {type} target id attribute
   * @param  {type} dir    svg resource url from where it is served
   * @memberof renderFeature
   */
  cargarPlano( target, dir){
    // target => id name
    d3.xml( dir).mimeType( "image/svg+xml").get( function( error, xml){
      if( error){ throw( error);}
      let div = document.getElementById(target);
      if(div != null){
        div.removeChild(div.childNodes[0]);
        div.appendChild(xml.documentElement);
      }
    });
  }

  /**
   * renderSala - Renders data on the svg resource <br>
   * Color applied to each element specified is provided by the function loaded in scaleColor.
   *
   * @param  {type} target DOM element from which it hangs the svg
   * @param  {type} data   TimeSeries processed data, in the form of pairs [metric, value]
   * @memberof renderFeature
   */
  renderSala (target, data){
    var t = d3.transition()
    .duration(750)
    .ease(d3.easeLinear);

    //ClearOutput
    var t = d3.select(target+' svg').selectAll( '.'+this.panel.render.elementIdentifyer).style( 'fill', this.panel.render.unknownDataColor);

    //Binding
    var salas = d3.select(target+' svg').selectAll( '.'+this.panel.render.elementIdentifyer)

    .data(data, function(d){ return d ? d.metric : this.id; });
    //Update
    salas
      .transition(t)
      .style('fill', $.proxy( function(d){ return this.scaleColor( d.value)}, this));
  }
}