UNPKG

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

Version:

MultiMetricComparisonBarChart - Domo Widget

505 lines (441 loc) 15.6 kB
var d3 = require('d3'); var d3Chart = require('d3.chart'); var BaseWidget = require('@domoinc/base-widget'); var SummaryNumber = require('@domoinc/summary-number'); var Utilities = require('@domoinc/utilities'); var daTheme2 = require('@domoinc/da-theme2'); //---------------------------------------------------------------------------------- //---------------------------------------------------------------------------------- // ComparisonBarChart: // This is where you can give some high level documentation of your widget. //---------------------------------------------------------------------------------- //---------------------------------------------------------------------------------- d3.chart('BaseWidget').extend('ComparisonBarChart', { //********************************************************************************** // Initialization //********************************************************************************** initialize: function() { 'use strict'; var _Chart = this; //---------------------------------------------------------------------------------- // CONFIG Helper Functions //---------------------------------------------------------------------------------- function minOfHeightAndWidth () { return d3.min([_Chart.c('height'), _Chart.c('width')]); } //---------------------------------------------------------------------------------- // Config //---------------------------------------------------------------------------------- this._newConfig = { chartName: { description: 'Name of chart for Reporting.', type: 'string', value: 'ComparisonBarChart' }, height: { description: 'Height of the chart.', value: 100, type: 'number' }, //---------------- // TEXT //---------------- textFontFamily: daTheme2.themeElements.h1FontFamily(), //---------------- // TITLE //---------------- titleWeight: { name: 'Title Font Weight', description: 'Font weight for the title text.', category: 'General', value: 700, type: 'number' }, title: { name: 'Title', description: 'Title on the top of the chart.', category: 'General', value: 'Tweets', type: 'string' }, titleSize: { name: 'Title Font Size', description: 'Font size of the title.', category: 'General', value: 24, type: 'number' }, titleColor: daTheme2.themeElements.h1FontColor({ name: 'Title Text Color', description: 'Color for the title text.' }), //---------------- // LABELS //---------------- labelSize: { name: 'Label Size', description: 'Font size of labels.', category: 'General', value: 12, type: 'number' }, labelColor: daTheme2.themeElements.h5FontColor(), //---------------- // BARS //---------------- chartPrimarySeriesColors: daTheme2.themeElements.primaryColorSeries(), barHeight: { name: 'Bar Height', description: 'Height of the comparison bar.', category: 'Bars', value: 30, type: 'number', percent: 0.7, dim: minOfHeightAndWidth, size: function() { return this.percent * this.dim(); }, units: 'px', updatePercent: function () { this.percent = this.value / this.dim(); } }, //---------------- // GENERAL //---------------- generalWashoutColor: daTheme2.themeElements.washoutColor(), showAnimations: { name: 'Show Animations', description: 'Whether or not to show animations', category: 'Animation', value: true, type: 'boolean' }, //Configs not in the plugin textLabel: { description: 'The function used for the text labels', value: function(d, i) { return _Chart.a('Label')(d) + ' ' + summaryNumber.summaryNumber(_Chart.a('Value')(d)); }, type: 'function' } }; this.mergeConfig(_Chart._newConfig); //---------------------------------------------------------------------------------- // Data Definition: //---------------------------------------------------------------------------------- _Chart._newDataDefinition = { 'Label': { type: 'string', validate: function(d) { return this.accessor(d) !== undefined && this.accessor(d); }, accessor: function(line) { return String(line[0]); } }, 'Value': { type: 'number', validate: function(d) { return !isNaN(this.accessor(d)) && this.accessor(d) >= 0 && this.accessor(d); }, accessor: function(line) { return Number(line[1]); } } }; 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._scale = d3.scale.linear(); _Chart._titleGroup = _Chart._layerGroup.append('g') .append('text') .attr({ 'class': 'title', 'text-anchor': 'middle', 'y': 0 }); _Chart._barsGroup = _Chart._layerGroup.append('g'); _Chart._labelGroup = _Chart._layerGroup.append('g'); //---------------------------------------------------------------------------------- // Triggers and event listeners //---------------------------------------------------------------------------------- _Chart.on('external:mouseover', highlightBar); _Chart.on('external:mouseout', highlightBothBars); _Chart.on('external:mouseover:left', hoverLeft); _Chart.on('external:mouseover:right', hoverRight); /*---------------------------------------------------------------------------------- // Data validation function and chart variable setup. ----------------------------------------------------------------------------------*/ //********************************************************************************** // d3.Chart Transform //********************************************************************************** _Chart.transform = function(data) { var validData = _Chart.validateData(data); _Chart.updateSizeableConfigs(); _Chart._labelGroup.attr('transform', 'translate(0,' + (_Chart.c('barHeight') + 10) + ')'); _Chart._barsGroup.attr('transform', 'translate(0,' + (5) + ')') initChartVariables(validData); setColorScale(); _Chart._data = validData; return validData }; //---------------------------------------------------------------------------------- // Layers: //---------------------------------------------------------------------------------- _Chart.layer('barsGroup', this._barsGroup, { dataBind: function(validData) { return this.selectAll('g.bars').data(validData); }, insert: function() { return this.append('g'); }, events: { 'enter': function() { this.append('rect') .attr({ 'class': function(d, i) { return i === 0 ? 'bar left' : 'bar right' }, 'width': 0, 'x': function(d, i) { return i === 0 ? 0 : _Chart.c('width') }, 'y': 0 }) .on('mouseenter', hover) .on('mouseleave', unhover); return this.classed('bars', true); }, 'merge': function() { this.select('rect') .style('fill', function(d, i) { return _colorScale(_Chart.a('Label')(d)); }); this.select('rect.bar') .transition('rectTransition') .duration(500) .attr({ 'height': _Chart.c('barHeight'),// * _Chart._config.barHeight.percent, 'width': function(d) { return _Chart._scale(_Chart.a('Value')(d)); }, 'x': function(d, i) { return i === 0 ? 0 : (_Chart.c('width') - _Chart._scale(_Chart.a('Value')(d))) } }) return this; }, 'exit': function() { return this.remove(); } } }); _Chart.layer('labelGroup', this._labelGroup, { dataBind: function(validData) { return this.selectAll('g.label').data(validData); }, insert: function() { return this.append('g'); }, events: { 'enter': function() { this.append('text') .attr({ 'class': function(d, i) { return i === 0 ? 'labelText left' : 'labelText right'; }, 'y': 0 }); return this.attr('class', function(d, i) { return i === 0 ? 'left label' : 'right label'; }); }, 'merge': function() { var sel = null; var padding = 10; this.select('text.labelText') .attr({ 'x': function(d, i) { return i === 0 ? 0 : _Chart.c('width'); }, 'y': function(d, i) { return _Chart.c('labelSize') * 0.70; }, 'text-anchor': function(d, i) { return i === 0 ? 'start' : 'end'; } }) .style({ 'font-size': _Chart.c('labelSize') + 'px', 'fill': _Chart.c('labelColor'), 'font-family': _Chart.c('textFontFamily') }) .each(function(d, i) { sel = d3.select(this); sel.text(_Chart.c('textLabel')(d, i)); d3.domoStrings.truncToFit(sel, (((_Chart.c('width') - padding) / 2) * 0.7)); //30% for the Amount }); return this; }, 'exit': function() { return this.remove(); } } }); //---------------------------------------------------------------------------------- // Event Functions //---------------------------------------------------------------------------------- /** * Triggers the mouseenter event. If _Chart.c('showAnimations') equals true, * sets the color of the labels and the bars to the generalWashoutColor * @param {[Array]} d Row of data used to draw the bar. */ function hover(data) { var obj = { series: _Chart.a('Value')(data), name: _Chart.a('Label')(data), data: data, }; _Chart.trigger('dispatch:mouseenter', obj, this); //jshint ignore:line if (_Chart.c('showAnimations')) { highlightBar(data); } } function highlightBar(data) { _Chart._barsGroup .selectAll('rect.bar') .filter(function(d) { return _Chart.a('Label')(data) !== _Chart.a('Label')(d); }) .transition('hoverTransition') .style('fill', _Chart.c('generalWashoutColor')); _Chart._barsGroup .selectAll('rect.bar') .filter(function(d) { return _Chart.a('Label')(data) === _Chart.a('Label')(d); }) .transition('hoverTransition') .style('fill', function(d, i) { return _colorScale(_Chart.a('Label')(d)); }) _Chart._labelGroup .selectAll('g.label') .filter(function(d) { return _Chart.a('Label')(data) !== _Chart.a('Label')(d); }) .selectAll('text.labelText') .transition('hoverTransition') .style('fill', _Chart.c('generalWashoutColor')); _Chart._labelGroup .selectAll('g.label') .filter(function(d) { return _Chart.a('Label')(data) === _Chart.a('Label')(d); }) .selectAll('text.labelText') .transition('hoverTransition') .style('fill', _Chart.c('labelColor')); } function hoverLeft() { highlightBar(_Chart._data[0]); } function hoverRight() { highlightBar(_Chart._data[1]); } /** * Triggers the mouseleave event. If _Chart.c('showAnimations') equals true, * sets the colors of the bars and labels to the original colors */ function unhover(d) { var obj; if (d) { obj = { series: _Chart.a('Value')(d), name: _Chart.a('Label')(d), data: d, } } _Chart.trigger('dispatch:mouseleave', obj, this); //jshint ignore:line if (_Chart.c('showAnimations')) { highlightBothBars(); } } function highlightBothBars() { _Chart._barsGroup .selectAll('rect.bar') .transition('hoverTransition') .style({ 'fill': function(d, i) { return _colorScale(_Chart.a('Label')(d)); }, 'opacity': 1 }); _Chart._labelGroup .selectAll('text.labelText') .transition('hoverTransition') .style('fill', _Chart.c('labelColor')); } //---------------------------------------------------------------------------------- // Helper Functions //---------------------------------------------------------------------------------- /** * Initiates the chart's variables * @param {[Array]} validData 2D array of data */ function initChartVariables(validData) { validRow(validData); updateScale(validData); drawTitle(); } /** * Removes any excess rows of data * @param {[Array]} validData 2D array of data */ function validRow(validData) { while (validData.length > 2) { validData.pop(); } } /** * Updateds the scale used for the bars * @param {[Array]} validData 2D array of data */ function updateScale(validData) { var sum = 0; for (var i = 0; i < validData.length; i++) { sum += _Chart.a('Value')(validData[i]); } _Chart._scale .domain([0, sum]) .range([0, _Chart.c('width')]); } /** * Sets the color scale for the chart */ function setColorScale() { if (!_manualColorScale) { _colorScale .domain([0, 1]) .range(daTheme2.getPrimaryColorsForNumberOfSeries(2, _Chart.c('chartPrimarySeriesColors'))); } } /** * Draws the title text of the chart */ function drawTitle() { var title = _Chart._titleGroup .attr('x', _Chart.c('width') / 2) .style({ 'font-family': _Chart.c('textFontFamily'), 'fill': _Chart.c('titleColor'), 'font-size': (_Chart.c('titleSize') + 2) + 'px', 'font-weight': _Chart.c('titleWeight') }) .text(_Chart.c('title')); d3.domoStrings.truncToFit(title, _Chart.c('width')); } //---------------------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------------------- _Chart.colorScale = function(_) { if (_) { _manualColorScale = true; _colorScale = _; return _Chart; } else { return _colorScale; } }; } });