@domoinc/multi-metric-comparison-bar-chart
Version:
MultiMetricComparisonBarChart - Domo Widget
505 lines (441 loc) • 15.6 kB
JavaScript
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;
}
};
}
});