@domoinc/multi-metric-comparison-bar-chart
Version:
MultiMetricComparisonBarChart - Domo Widget
495 lines (431 loc) • 17.6 kB
JavaScript
/*! 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__;
/***/ }
/******/ ])
});
;