dvf-2.x-dev/js/jquery.dvfCharts.js

js/jquery.dvfCharts.js
;(function ($, bb, once) {

  'use strict';

  var pluginName = 'dvfCharts',
      defaults = {};

  /**
   * Plugin constructor.
   */
  function Plugin(element, options) {
    this.element = element;
    this.options = $.extend( {}, defaults, options);
    this.config = {};

    this._defaults = defaults;
    this._name = pluginName;

    this.init();
  }

  /**
   * Plugin prototype object.
   */
  Plugin.prototype = {

    init: function () {
      this
        .parseChartOptions()
        .parseDataOptions()
        .parseAxisOptions()
        .parseGridOptions()
        .parseLegendOptions()
        .parsePointOptions()
        .parseBarOptions()
        .parseGaugeOptions()
        .parseColumnOverrideOptions()
        .parseConfigOverrides()
        .generateChart()
        .addToggleTableButton()
        .addDownloadButtons();
    },

    /**
     * Calls the billboard third party plugin with the parsed config.
     *
     * @returns {Plugin}
     */
    generateChart: function () {
      bb.generate(this.config);
      return this;
    },

    /**
     * Parses the chart options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parseChartOptions: function () {
      var plugin = this;

      this.config.bindto = this.element;

      $.each(['width', 'height'], function (m, measure) {
        if (plugin.options.chart.styles[measure]) {
          plugin.config.size = plugin.config.size || {};
          plugin.config.size[measure] = parseInt(plugin.options.chart.styles[measure]);
        }
      });

      $.each(['top', 'right', 'bottom', 'left'], function (e, edge) {
        if (plugin.options.chart.styles.padding[edge]) {
          plugin.config.padding = plugin.config.padding || {};
          plugin.config.padding[edge] = parseInt(plugin.options.chart.styles.padding[edge]);
        }
      });

      this.config.interaction = { enabled: this.options.chart.interaction };

      if (plugin.options.chart.palette) {
        this.config.color = {
          pattern: plugin.options.chart.palette.split(',').map(function(color) {
            return color.trim();
          })
        };
      }

      // Display chart title is title.show is true.
      if (plugin.options.chart.title.show) {
        this.config.title = { text: plugin.options.chart.title.text };
      }

      return this;
    },

    /**
     * Parses the data options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parseDataOptions: function () {
      var data = { columns: this.options.chart.data.columns };

      if (this.options.axis.x.type === 'timeseries' && this.options.axis.x.tick.format.timeseries.input) {
        data.xFormat = this.options.axis.x.tick.format.timeseries.input;
      }

      data.type = this.options.chart.data.type;

      if (this.options.chart.data.stacked) {

        // Assign data groups depending on x axis grouping value.
        data.groups =
          this.options.axis.x.x_axis_grouping === 'values' ?
          [$.map(data.columns, function(group) { return group[0]; })] :
          [$.map(this.options.chart.data.groups, function(g, group) { return [group]; })];

        data.order = this.options.chart.data.order;
      }

      if (this.options.axis.x.tick.values.custom) {
        data.x = 'x';
        data.columns.unshift(['x'].concat(this.options.axis.x.tick.values.custom));
      }

      if (this.options.chart.data.names) {
        data.names = this.options.chart.data.names;
      }

      data.labels = !!this.options.chart.data.labels.show;

      this.config.data = data;

      return this;
    },

    /**
     * Parses the axis options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parseAxisOptions: function () {
      var plugin = this, axis = {};

      axis.rotated = this.options.axis.styles.rotated;

      axis.x = {
        type: this.options.axis.x.type,
        tick: {
          centered: this.options.axis.x.styles.tick.centered,
          multiline: this.options.axis.x.styles.tick.multiline
        }
      };

      if (this.options.axis.x.type === 'indexed' && this.options.axis.x.tick.format.indexed) {
        axis.x.tick.format = function (x) {
          return plugin.formatNumber(x, plugin.options.axis.x.tick.format.indexed);
        };
      }

      if (this.options.axis.x.type === 'timeseries' && this.options.axis.x.tick.format.timeseries.output) {
        axis.x.tick.format = this.options.axis.x.tick.format.timeseries.output;
      }

      if (this.options.axis.x.type === 'indexed' && this.options.axis.x.tick.culling) {
        axis.x.tick.culling = { max: parseInt(this.options.axis.x.tick.culling) };
        axis.x.tick.format = function (x) {
          return Math.round(x);
        };
      }

      if (this.options.axis.x.tick.count) {
        axis.x.tick.count = parseInt(this.options.axis.x.tick.count);
      }

      if (this.options.axis.x.styles.tick.rotate) {
        axis.x.tick.rotate = parseInt(this.options.axis.x.styles.tick.rotate);
      }

      if (this.options.axis.x.styles.tick.width) {
        axis.x.tick.width = this.options.axis.x.styles.tick.width;
      }

      axis.x.label = {
        text: this.options.axis.x.label.text,
        position: (this.options.axis.styles.rotated) ? this.options.axis.y.styles.label.position : this.options.axis.x.styles.label.position
      };

      axis.y = {
        type: this.options.axis.y.type,
        tick: {}
      };

      axis.y.tick.format = function (y) {
        if (plugin.options.axis.y.type !== 'indexed') {
          return y;
        }

        var number = plugin.maxRound(y, 4);

        if (plugin.options.axis.y.tick.rounding) {
          number = number.toFixed(parseInt(plugin.options.axis.y.tick.rounding));
        }

        if (plugin.options.axis.y.tick.format.indexed) {
          number = plugin.formatNumber(number, plugin.options.axis.y.tick.format.indexed);
        }

        return number;
      };

      if (this.options.axis.y.tick.count) {
        axis.y.tick.count = parseInt(this.options.axis.y.tick.count);
      }

      if (this.options.axis.y.tick.values.custom) {
        axis.y.tick.values = this.options.axis.y.tick.values.custom;
      }

      if (this.options.axis.y.styles.padding && typeof(this.options.axis.y.styles.padding) == 'object') {
        axis.y.padding = this.options.axis.y.styles.padding;
      }

      if (this.options.axis.x.styles.padding && typeof(this.options.axis.x.styles.padding) == 'object') {
        axis.x.padding = this.options.axis.x.styles.padding;
      }

      axis.y.label = {
        text: this.options.axis.y.label.text,
        position: (this.options.axis.styles.rotated ? this.options.axis.x.styles.label.position: this.options.axis.y.styles.label.position)
      };

      this.config.axis = axis;

      return this;
    },

    /**
     * Parses the grid options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parseGridOptions: function () {
      var grid = {
        x: {
          show: this.options.grid.x.show,
          lines: []
        },
        y: {
          show: this.options.grid.y.show,
          lines: []
        }
      };

      $.each(this.options.grid.lines, function(l, line) {
        grid[line.axis].lines.push(line);
      });

      this.config.grid = grid;

      return this;
    },

    /**
     * Parses the legend options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parseLegendOptions: function () {
      var legend = {};

      if (!this.options.chart.data.legends.interaction) {
        legend.item = {
          onclick: function () {
            return false;
          },
          onmouseover: function () {
            return false;
          },
          onmouseout: function () {
            return false;
          }
        };
      }

      this.config.legend = $.isEmptyObject(legend) ? null : legend;

      return this;
    },

    /**
     * Parses the point options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parsePointOptions: function () {
      var point = {},
          pointShow = this.getDeepProperty(this.options, 'point.show'),
          pointRadius = this.getDeepProperty(this.options, 'point.radius');

      point.show = !!pointShow;

      if (pointRadius) {
        point.r = parseFloat(pointRadius);
      }

      this.config.point = $.isEmptyObject(point) ? null : point;

      return this;
    },

    /**
     * Parses the bar chart options and sets them to the config.
     *
     * @returns {Plugin}
     */
    parseBarOptions: function () {
      var bar = {},
          barRatio = this.getDeepProperty(this.options, 'bar.width.ratio'),
          barValue = this.getDeepProperty(this.options, 'bar.width.value');

      if (barRatio && barRatio !== 'manual') {
        bar.width = { ratio: parseFloat(barRatio) };
      }

      if (barValue && barRatio === 'manual') {
        bar.width = parseInt(barValue);
      }

      this.config.bar = $.isEmptyObject(bar) ? null : bar;

      return this;
    },

    /**
     * Parse user settings into object suitable for use with bb Gauge Chart.
     *
     * @example https://naver.github.io/billboard.js/demo/#Chart.GaugeChart
     * @see https://naver.github.io/billboard.js/release/latest/doc/Options.html#.gauge
     *
     * @returns {Plugin}
     */
    parseGaugeOptions: function () {
      var gauge = {},
        gaugeLabelShow = this.getDeepProperty(this.options, 'gauge.label.show'),
        gaugePercentage = this.getDeepProperty(this.options, 'gauge.label.percentage'),
        gaugeMin = this.getDeepProperty(this.options, 'gauge.min'),
        gaugeMax = this.getDeepProperty(this.options, 'gauge.max'),
        gaugeUnits = this.getDeepProperty(this.options, 'gauge.units'),
        gaugeWidth = this.getDeepProperty(this.options, 'gauge.width');
        gauge.label = {
          format: false,
          show: false
        };

      if (gaugeLabelShow) {
        gauge.label.show = gaugeLabelShow;
      }

      if (!gaugePercentage) {
        gauge.label.format = function(value, ratio) { return value; }
      }

      if (gaugeMin) {
        gauge.min = gaugeMin;
      }

      if (gaugeMax) {
        gauge.max = gaugeMax;
      }

      if (gaugeUnits) {
        gauge.units = gaugeUnits;
      }

      if (gaugeWidth) {
        gauge.width = gaugeWidth;
      }

      this.config.gauge = $.isEmptyObject(gauge) ? null : gauge;

      return this;
    },

    /**
     * Allow overrides to be merged. Assumes correctly structured object.
     */
    parseConfigOverrides: function () {
      if (typeof this.options.overrides === 'object') {
        $.extend(true, this.config, this.options.overrides);
      }

      return this;
    },

    /**
     * Adds the various download buttons below the chart.
     *
     * @returns {Plugin}
     */
    addDownloadButtons: function () {
      if (typeof $.fn.chartExport !== 'function') {
        return this;
      }

      var self = this,
          buttonTypes = ['png', 'svg'],
          $chartWrapper = this.element.closest('.dvf--wrapper');

      // For each chart, add download buttons.
      once('download-buttons', $chartWrapper ).forEach(el => {
        var $buttonWrapper = $(el).find('.table-chart--actions'),
          $chart = $(el).find('.dvf-chart svg');

        // Add different button types.
        $(buttonTypes).each(function (i, format) {
          $('<button/>')
            .html('Download as ' + format.toUpperCase())
            .addClass('dvf-chart--download')
            .addClass(self.options.chart.component + '--download')
            .chartExport($.extend({
              format: format,
              svg: $chart.is('svg') ? $chart : $chart.find('svg'),
            }, {
              // Add optional settings if they exist.
              width: self.options.chart.styles.width || undefined,
              height: self.options.chart.styles.height || undefined,
              filename: (self.config.title && self.config.title.text) ? self.config.title.text : undefined,
            }))
            .appendTo($buttonWrapper);
        })

        once('download-data', '.table-chart--actions .download-data').forEach(element => {
          $(element).on('click', function() {
            window.open($(element).data('file-uri'));
          });
        })
      });

      return this;
    },

    /**
     * Add toggle table / chart button.
     *
     * @returns {Plugin}
     */
    addToggleTableButton: function () {
      var $chartWrapper = this.element.closest('.dvf--wrapper');

      // For each chart, add download buttons.
      once('table-toggle', $chartWrapper).forEach(el => {
        var $buttonWrapper = $(el).find('.table-chart--actions');

        if ( $('.dvf-table', el).length === 0) {
          return;
        }

        $('<button/>')
          .html('Show table')
          .addClass('table-chart--toggle')
          .click(function() {
            $('.dvf-chart, .dvf-table, button.dvf-chart--download', el).toggleClass('visually-hidden');
            $(this).text($(this).text().toLowerCase().trim() === 'show chart' ? 'Show table' : 'Show chart');
          })
          .appendTo($buttonWrapper);
      })

      return this;
    },

    /**
     * Parses the column override settings and applies them accordingly.
     *
     * @returns {Plugin}
     */
    parseColumnOverrideOptions: function () {
      // If column overrides do not exist, exit without processing.
      if (this.options.chart.data.column_overrides === undefined) {
        return this;
      }

      var column_overrides = this.options.chart.data.column_overrides;

      // Colors.
      this.overrideDataSetting(column_overrides, 'color');

      // Types.
      this.overrideDataSetting(column_overrides, 'type');

      // Label.
      this.overrideDataSetting(column_overrides, 'label', 'names');

      // Styles.
      this.overrideDataSetting(column_overrides, 'style', 'regions');

      // Classes.
      this.overrideDataSetting(column_overrides, 'class', 'classes');

      // Weight.
      var ordered_columns = [];
      $.each(column_overrides, function(i, overrides) {
        if (overrides.weight !== undefined) {
          $.each(this.config.data.columns, function(key, column) {
            if (overrides.key === column[0]) {
              ordered_columns.push(column);
            }
          });
        }
      }.bind(this));

      if (this.config.data.columns.length && this.config.data.columns[0].length) {
        if (this.config.data.columns[0][0] === 'x' && this.config.data.columns.length !== ordered_columns.length) {
          ordered_columns.unshift(this.config.data.columns[0]);
        }
      }

      if (this.config.data.columns.length === ordered_columns.length) {
        this.config.data.columns = ordered_columns;
      }

      // Legend.
      $.each(column_overrides, function(key, overrides) {
        if (overrides.legend) {
          this.config.legend = this.config.legend || [];
          this.config.legend[overrides.legend] = this.config.legend[overrides.legend] || [];
          this.config.legend[overrides.legend].push(key);
        }
      }.bind(this));

      return this;
    },

    /**
     * Sets custom settings or overrides for config.data.X
     *
     * @param column_overrides
     *   The array of settings to get the data from.
     * @param override_key
     *   The key to look for in the above array.
     * @param config_key
     *   The key to set the new settings to in the config array.
     *
     * @returns {Plugin}
     */
    overrideDataSetting: function (column_overrides, override_key, config_key) {

      config_key = config_key || override_key + 's';

      $.each(column_overrides, function(key, overrides) {
        if (overrides[override_key]) {
          this.config.data[config_key] = this.config.data[config_key] || {};
          this.config.data[config_key][key] = overrides[override_key];
        }
      }.bind(this));

      return this;
    },

    /**
     * Returns a rounded up number to a set amount of decimal points.
     *
     * @param number
     *   The number to be rounded.
     * @param decimals
     *   The number of decimal places.
     * @returns {number}
     *   The rounded number.
     */
    maxRound: function (number, decimals) {
      return Math.round(number * Math.pow(10, decimals)) / Math.pow(10, decimals);
    },

    /**
     * Returns a number with a separator format applied (e.g. 12 345 vs 12,345).
     *
     * @param number
     *   The number to format.
     * @param separator
     *   The separator.
     * @returns {string}
     *   The formatted number.
     */
    formatNumber: function (number, separator) {
      return number.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + separator);
    },

    /**
     * Gets a property nested in a JS object.
     *
     * @param obj
     *   The object to traverse.
     * @param key
     *   The key buried in the object.
     * @returns {*}
     *   The result of the search.
     */
    getDeepProperty: function(obj, key) {
      return key.split('.').reduce(function (o, k) {
        return (typeof o === 'undefined' || o === null) ? o : o[k];
      }, obj);
    }

  };

  /**
   * Plugin wrapper.
   */
  $.fn[pluginName] = function (options) {
    return this.each(function () {
      if (!$.data(this, 'plugin_' + pluginName)) {
        $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
      }
    });
  };

})(jQuery, bb, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc