UNPKG

@domoinc/multi-metric-comparison-bar-chart

Version:

MultiMetricComparisonBarChart - Domo Widget

495 lines (431 loc) 17.6 kB
/*! Copyright 2016 Domo Inc. */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("d3"), require("d3.chart"), require("@domoinc/base-widget"), require("@domoinc/summary-number"), require("@domoinc/da-theme2"), require("@domoinc/comparison-bar-chart")); else if(typeof define === 'function' && define.amd) define(["d3", "d3.chart", "base-widget", "summary-number", "da-theme2", "comparison-bar-chart"], factory); else if(typeof exports === 'object') exports["MultiMetricComparisonBarChart"] = factory(require("d3"), require("d3.chart"), require("@domoinc/base-widget"), require("@domoinc/summary-number"), require("@domoinc/da-theme2"), require("@domoinc/comparison-bar-chart")); else root["MultiMetricComparisonBarChart"] = factory(root["d3"], root["d3.chart"], root["BaseWidget"], root["SummaryNumber"], root["da"]["theme2"], root["ComparisonBarChart"]); })(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_5__, __WEBPACK_EXTERNAL_MODULE_6__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = "/dist/"; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var d3 = __webpack_require__(1); var d3Chart = __webpack_require__(2); var BaseWidget = __webpack_require__(3); var SummaryNumber = __webpack_require__(4); var daTheme2 = __webpack_require__(5); var ComparisonBarChart = __webpack_require__(6); /*---------------------------------------------------------------------------------- ---------------------------------------------------------------------------------- // MultiMetricComparisonBarChart: ---------------------------------------------------------------------------------- ----------------------------------------------------------------------------------*/ module.exports = BaseWidget.extend('MultiMetricComparisonBarChart', { initialize: function initialize() { 'use strict'; var _Chart = this; _Chart._dataObject = {}; //---------------------------------------------------------------------------------- // Config //---------------------------------------------------------------------------------- this._newConfig = { //plugin user configable configs width: { name: 'Widget Width', description: 'Width of the widget', value: 400, type: 'number', units: 'px' }, height: { // name: 'Widget Height', description: 'Height of the widget', value: 400, type: 'number', units: 'px' }, barHeight: { name: 'Bar Height', description: 'Height of the bars', category: 'Bars', value: 30, type: 'number', units: 'px' }, chartGap: { name: 'Bar Spacing', description: 'The space between the bars', value: 10, type: 'number', units: 'px' }, generalWashoutColor: daTheme2.themeElements.washoutColor(), //---------------- // TEXT //---------------- textFontFamily: daTheme2.themeElements.h1FontFamily(), //---------------- // TITLE //---------------- titleWeight: { name: 'Title Font Weight', description: 'Font weight for the title text', category: 'General', value: 700, type: 'number', units: '' }, titleSize: { name: 'Title Font Size', description: 'Font size for the titles', category: 'General', value: 12, type: 'number', units: 'px' }, titleColor: daTheme2.themeElements.h1FontColor({ name: 'Title Font Color', description: 'Font color for the titles' }), //---------------- // LABELS //---------------- labelSize: { name: 'Label Font Size', description: 'Font size for the labels', category: 'General', value: 12, type: 'number', units: 'px' }, labelColor: daTheme2.themeElements.h5FontColor({ name: 'Label Font Color', description: 'Font color for the labels' }), //---------------- // GENERAL //---------------- showAnimations: { name: 'Enable Animation', description: 'Enable or disable the animation', category: 'Animation', type: 'select', value: { name: 'Show', value: true }, options: [{ name: 'Show', value: true }, { name: 'Hide', value: false }] }, //widget default configs chartName: { description: 'Name of chart for reporting', type: 'string', value: 'MultiMetricComparisonBarChart' }, chartPrimarySeriesColors: daTheme2.themeElements.primaryColorSeries(), //Configs not in the plugin textLabel: { description: 'The function used for the text labels', value: function value(d, i) { return d[0] + ' ' + summaryNumber.summaryNumber(d[1]); }, type: 'function' } }; this.mergeConfig(_Chart._newConfig); //---------------------------------------------------------------------------------- // Data Definition: //---------------------------------------------------------------------------------- _Chart._newDataDefinition = { 'Metric': { type: 'string', validate: function validate(d) { return this.accessor(d) !== undefined; }, accessor: function accessor(line) { return String(line[0]); } }, 'Label': { type: 'string', validate: function validate(d) { return this.accessor(d) !== undefined; }, accessor: function accessor(line) { return String(line[1]); } }, 'Value': { type: 'number', validate: function validate(d) { return !isNaN(this.accessor(d)) && this.accessor(d) > 0; }, accessor: function accessor(line) { return Number(line[2]); } } }; this.mergeDataDefinition(_Chart._newDataDefinition); //---------------------------------------------------------------------------------- // Static - Anything that is defined here will never change // If extending 'BaseWidget' : _Chart._layerGroup is the NEW BASE //---------------------------------------------------------------------------------- var summaryNumber = new SummaryNumber(); var _manualColorScale = false; var _colorScale = d3.scale.ordinal(); _Chart._chartGroup = _Chart._layerGroup.append('g').attr('class', 'chartGroup'); _Chart.transform = function (data) { var validData = _Chart.validateData(data); calculateTextHeight(); moveNotifier(); initChartVariables(validData); setColorScale(validData); return validData; }; //---------------------------------------------------------------------------------- // Layers: //---------------------------------------------------------------------------------- _Chart.layer('chartGroup', this._chartGroup, { dataBind: function dataBind(validData) { var data = validData.metrics; return this.selectAll(' g.chart').data(data); }, insert: function insert() { return this.append('g'); } }).on('enter', function () { // adds chart to group this.each(function (d, i) { this.__chart__ = d3.select(this).chart('ComparisonBarChart').on('dispatch:mouseenter', hover).on('dispatch:mouseleave', unhover); this.__chart__.colorScale(_Chart.colorScale()); }); return this.classed('chart', true); }).on('merge', function () { this.attr('transform', function (d, i) { return 'translate(0,' + i * (_Chart.innerChartHeight + _Chart.c('chartGap')) + ')'; }); this.each(function (d, i) { this.__chart__.c({ 'height': _Chart.innerChartHeight, 'width': _Chart.c('width'), 'barHeight': _Chart.c('barHeight'), 'chartPrimarySeriesColors': _Chart.c('chartPrimarySeriesColors'), 'textFontFamily': _Chart.c('textFontFamily'), 'title': d, 'titleWeight': _Chart.c('titleWeight'), 'titleSize': _Chart.c('titleSize'), 'titleColor': _Chart.c('titleColor'), 'labelSize': _Chart.c('labelSize'), 'labelColor': _Chart.c('labelColor'), 'generalWashoutColor': _Chart.c('generalWashoutColor'), 'showAnimations': _Chart.c('showAnimations').value, 'textLabel': _Chart.c('textLabel') }).draw(_Chart._dataObject[d]); }); return this; }).on('exit', function () { return this.remove(); }); //---------------------------------------------------------------------------------- // Event Functions //---------------------------------------------------------------------------------- /** * Sets off dispatch:mouseenter:bar trigger event. If _Chart.c('showAnimations') is true, * it changes the color of the opposite bar to generaWashoutColor * @param {[Object]} obj Object with series, name, and data properties * @param {[SVG]} element SVG element that is being hovered over */ function hover(obj, element) { var thisClass = d3.select(element).classed('left') ? 'left' : 'right'; _Chart.trigger('dispatch:mouseenter:bar', obj, element); var trigger = 'external:mouseover:' + thisClass; if (_Chart.c('showAnimations').value) { _Chart._chartGroup.selectAll('.chart').each(function () { this.__chart__.trigger(trigger); }); } } /** * Sets off dispatch:mouseleave:bar trigger event. If _Chart.c('showAnimations') is true, * it changes the color of the opposite bar to its original color * @param {[SVG]} element SVG element that was hovered off of */ function unhover(obj, element) { _Chart.trigger('dispatch:mouseleave:bar', obj, element); if (_Chart.c('showAnimations').value) { _Chart._chartGroup.selectAll('.chart').each(function () { this.__chart__.trigger('external:mouseout'); }); } } //---------------------------------------------------------------------------------- // Helper Functions //---------------------------------------------------------------------------------- /** * Initiates the charts variables * @param {[Array]} validData 2D array of data * @return {[Array]} validDAta 2D array of data with a metrics property */ function initChartVariables(validData) { // saves the data in this object with a hierarchy _Chart._dataObject = {}; // passes only the metrics onto the layer var metrics = []; // loop through to add hieararchy getMetrics(metrics, validData); var chartSize = metrics.length * (_Chart.innerChartHeight + _Chart.c('chartGap')); var excessData = []; //Remove excess data from metrics removeExcessData(excessData, chartSize, metrics); //If there is excess data, notify the user. excessDataNotifier(excessData); validData.metrics = metrics; return validData; } /** * Removes excess data so the chart doesn't draw more bars than what the height can hold * @param {[Array]} excessData Empty array * @param {[Number]} chartSize Size of the chart to accomodate all of the bars drawn * @param {[Array]} metrics Array of strings that represent the metrics that are being measured */ function removeExcessData(excessData, chartSize, metrics) { while (chartSize > _Chart.c('height')) { excessData.push(metrics.pop()); chartSize = metrics.length * (_Chart.innerChartHeight + _Chart.c('chartGap')); } } /** * Notifies the user if any of the data is removed * @param {[Array]} excessData Array of the excess data */ function excessDataNotifier(excessData) { if (excessData.length > 0) { _Chart._notifier.base.attr('transform', 'translate(0,-20)'); _Chart._notifier.appendMessage(_Chart.c('chartName'), 'DISCARDED_VALID_DATA', 'There are too many bars for chart\'s height!', ['Metric:String', 'Label:String', 'Value:Number'], excessData); _Chart._notifier.draw(); } } /** * Loops through the data and sets its metrics and _Chart._dataObject * @param {[Array]} metrics Empty array * @param {[Array]} validData 2D array of data */ function getMetrics(metrics, validData) { for (var i = 0; i < validData.length; i++) { var metric = _Chart.a('Metric')(validData[i]); var array = [_Chart.a('Label')(validData[i]), _Chart.a('Value')(validData[i])]; // checks if metric already exists in object if (_Chart._dataObject[metric] === undefined) { _Chart._dataObject[metric] = []; metrics.push(metric); } // adds array under metric // only allows 2 per metric if (_Chart._dataObject[metric].length <= 2) { _Chart._dataObject[metric].push(array); } else { _Chart._notifier.appendMessage(_Chart.c('chartName'), 'WARN', 'Too many rows', validData[i]); } } } /** * Set the color scale for the chart */ function setColorScale(data) { var uniqueLabels; if (!_manualColorScale) { uniqueLabels = data.map(function (d) { return _Chart.a('Label')(d); }).filter(function (el, i, a) { return i === a.indexOf(el); }); _colorScale = d3.scale.ordinal().domain(uniqueLabels).range(daTheme2.getPrimaryColorsForNumberOfSeries(uniqueLabels.length, _Chart.c('chartPrimarySeriesColors'))); } } function calculateTextHeight() { var title = _Chart._layerGroup.append('text').classed('tempText', true).text('title').style('font-size', _Chart.c('titleSize')).style('font-family', 'Open Sans').style('opacity', 0); var label = _Chart._layerGroup.append('text').text('label').classed('tempText', true).style('font-size', _Chart.c('labelSize')).style('font-family', 'Open Sans').style('opacity', 0); var titleHeight = title.node().getBBox().height * 0.9; var labelHeight = label.node().getBBox().height * 0.9; _Chart.titleHeight = titleHeight; _Chart.textHeight = titleHeight + labelHeight; _Chart._layerGroup.selectAll('.tempText').remove(); _Chart.innerChartHeight = _Chart.c('barHeight') + _Chart.textHeight; } function moveNotifier() { _Chart._notifier.base.attr('transform', 'translate(0,' + -_Chart.titleHeight + ')'); } //---------------------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------------------- _Chart.colorScale = function (_) { if (_) { _manualColorScale = true; _colorScale = _; return _Chart; } else { return _colorScale; } }; } }); /***/ }, /* 1 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_1__; /***/ }, /* 2 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_2__; /***/ }, /* 3 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_3__; /***/ }, /* 4 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }, /* 5 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_5__; /***/ }, /* 6 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_6__; /***/ } /******/ ]) }); ;