Source

widgets/sankey.js

import processStructure from '@devoinc/applications-data-library/structures/sankey';
import widgetFactory from '@devoinc/applications-builder/widgetFactory';
import dependencies from '../data/dependencies';

const SankeyWidget = dependencies.require('widgets').SankeyWidget;

/**
 * This is a flow diagram from source to target where the width of the arrows
 * is proportional to the flow quantity.
 *
 * This widget is based on
 * [D3 Sankey Diagram]{@link https://github.com/d3/d3-sankey}
 * library.
 *
 * @category Widgets
 * @module Sankey
 * @see [base](module-base.html)
 * @see [collapser](module-collapser.html)
 * @see [dataSearch](module-dataSearch.html)
 * @see [download](module-download.html)
 * @see [info](module-info.html)
 * @see [lifeCycle](module-lifeCycle.html)
 * @see [listeners](module-listeners.html)
 * @see [loading](module-loading.html)
 * @see [menu](module-menu.html)
 * @see [screenshot](module-screenshot.html)
 * @see [zoom](module-zoom.html)
 * @tutorial widgets-sankey
 */
function mixin(self) {
  return {
    /**
     * Set the value key.
     * @param {string} key - Text of the key.
     * @instance
     */
    setValKey(key) {
      self.settings.valKey = key;
    },

    /**
     * Set the key of outgoing link.
     * @param {string} key - Text of the key.
     * @instance
     */
    setSourceKey(key) {
      self.settings.sourceKey = key;
    },

    /**
     * Set the key of incoming link.
     * @param {String} key - Text of the key.
     * @instance
     */
    setTargetKey(key) {
      self.settings.targetKey = key;
    },

    /**
     * Set the margin value that wraps the chart.
     * @param {number} margin - Value of margin in pixels.
     * @instance
     */
    setMargin(margin) {
      self.settings.margin = margin;
    },

    /**
     * Set the width of nodes.
     * @param {number} nodeWidth - Value of width in pixels.
     * @instance
     */
    setNodeWidth(nodeWidth) {
      self.settings.nodeWidth = nodeWidth;
    },

    /**
     * Set the vertical separation between adjacent nodes.
     * @param {number} num - Value of padding.
     * @instance
     */
    setNodePadding: (num) => (self.settings.nodePadding = num),

    /**
     * Set node text background filter name.
     * This method needs to set <i>setGenerateNodeTextBackground</i>.
     * @param {string} filterName - Text.
     * @instance
     */
    setNodeTextBackgroundFilterName(filterName) {
      self.settings.nodeTextBackgroundFilterName = filterName;
    },

    /**
     * Set the filter propperty style as visual effect to a node.
     * @param {function|string} ref - Filter property.
     * @instance
     */
    setNodeTextBackgroundFilterRef(ref) {
      self.settings.nodeTextBackgroundFilterRef = ref;
    },

    /**
     * Apply the background filter name in the node.
     * This method needs to set <i>setNodeTextBackgroundFilterName</i>.
     * @param {Boolean} bool - Apply the filter.
     * @instance
     */
    setGenerateNodeTextBackground(bool) {
      self.settings.generateNodeTextBackground = bool;
    },

    /**
     * Set the links color.
     *
     * The following values are supported:
     * - <b>source</b>
     * - <b>target</b>
     * - <b>source-target</b>
     * or an static color.
     * @param {string} color - Text with the color.
     * @instance
     */
    setLinkColor(color) {
      self.settings.linkColor = color;
    },

    /**
     * Set cycles.
     *
     * @param {Function} func - Callback JavaScript function.
     * @instance
     */
    setCycles(func) {
      self.settings.cycles = func;
    },

    /**
     * Apply a percentile score value.
     * @param {number} num - Number between 0 and 1.
     * @instance
     */
    setPercentile: (num) => (self.settings.percentile = num),

    /**
     * Apply a weight to the data to apply the percentile.
     * @param {number} num - Weight value.
     * @instance
     */
    setWeightedPercentile(num) {
      self.settings.weightedPercentile = num;
    },

    /**
     * Set tail name.
     * @param {string} str - Text with the name.
     * @instance
     */
    setTailName(str) {
      self.settings.tailName = str;
    },

    /**
     * Set the nodes breadths.
     *
     * The following values are supported:
     * - <i>forward</i>
     * - <i>backward</i>
     * or a function.
     * @param {string|Function} val - Value to apply.
     * @instance
     */
    setBreadths(val) {
      self.settings.breadths = val;
    },

    /**
     * Sets the aligment of the chart.
     * When the breath value is "<i>forward</i>", it's aligned to the right.
     * When the breath value is "<i>backward</i>", it's aligned to the left.
     * @param {boolean} bool - Align the chart.
     * @instance
     */
    setAlign(bool) {
      self.settings.align = bool;
    },

    /**
     * Sets the number of relaxation iterations when generating the layout.
     * @param {number} num - Numbers of iterations.
     * @instance
     */
    setIterations(num) {
      self.settings.iterations = num;
    },

    /**
     * Transform outer-join data to inner-join data.
     * This is, it leaves only the nodes and links that are part of paths
     * from the nodes of the first block to the nodes of the last block.
     * @param {boolean} bool - Enable this transformation.
     * @instance
     */
    setInnerJoinOnInitialValue(bool) {
      self.settings.innerJoinOnInitialValue = bool;
    },

    /**
     * Set statistics on initial value.
     * @param {boolean} bool - Enable statistics..
     * @instance
     */
    setStatsOnInitialValue(bool) {
      self.settings.statsOnInitialValue = bool;
    },

    /**
     * Add visible title to nodes.
     * @param {boolean} bool - Enable title.
     * @instance
     */
    setWithNodeLabels(bool) {
      self.settings.withNodeLabels = bool;
    },

    /**
     * Set visible link labels.
     * @param {boolean} bool - Enable link labels.
     * @instance
     */
    setWithLinkLabels(bool) {
      self.settings.withLinkLabels = bool;
    },

    /**
     * Set visible node names.
     * @param {boolean} bool - Enable node names.
     * @instance
     */
    setWithNodeNames(bool) {
      self.settings.withNodeNames = bool;
    },

    /**
     * Set with inner join toggle.
     * #todo
     * @param {boolean} bool - Enable inner join.
     * @instance
     */
    setWithInnerJoinToggle(bool) {
      self.settings.withInnerJoinToggle = bool;
    },

    /**
     * Set with stats toggle.
     * #todo
     * @param {boolean} bool - Enable inner join.
     * @instance
     */
    setWithStatsToggle(bool) {
      self.settings.withStatsToggle = bool;
    },

    /**
     * Set the info margin.
     * @param {number} num - Number in pixels.
     * @instance
     */
    setInfoMargin(num) {
      self.settings.infoMargin = num;
    },

    // Life Cycle
    // ---------------------------------------------------------------------------

    /**
     * Render function
     * @param {object} orig - Data for process
     * @ignore
     */
    render: function (orig) {
      if (!self.el) return; // If not have element not render
      //let wrapper = document.createElement('div');
      //wrapper.className += 'lt-lx-aw-sankey';
      self.el.querySelector('.lt-vapp-widget-graphic').className +=
        ' lt-lx-aw-sankey';
      let cfg = Object.assign({}, self.settings);
      let data = processStructure(
        orig,
        cfg.valKey,
        cfg.sourceKey,
        cfg.targetKey
      );
      cfg.withDownloadButton = false;
      if (data) {
        self.widget = new SankeyWidget(cfg);
        this.processOn(self);
        self.widget.setData(data);
        self.widget.display({
          force: true,
          data: true,
        });
      } else {
        this.debugError({
          msg: 'NO DATA',
          console: {
            method: 'error',
            msg: 'No data arrive to render function',
          },
        });
      }
    },

    /**
     *
     * @param {*} self
     * @ignore
     */
    processOn(self) {
      function marking() {
        self.widget._domContainer.addClass('lt-lx-aw-sankey-marking');
      }

      function normal() {
        self.widget._domContainer.removeClass('lt-lx-aw-sankey-marking');
      }

      var _settings = {
        on: {
          nodeover: [
            marking,
            function (d) {
              self.widget.markNode(d, 'over', Infinity);
            },
          ],
          nodeout: [
            normal,
            function () {
              self.widget.unmarkAll('over');
            },
          ],
          linkover: [
            marking,
            function (d) {
              self.widget.markLink(d, 'over', Infinity);
            },
          ],
          linkout: [
            normal,
            function () {
              self.widget.unmarkAll('over');
            },
          ],
        },
      };
      self.widget.configure(_settings);
    },
  };
}

export default widgetFactory(mixin);