@domoinc/multiline-chart
Version:
MultiLineChart - Domo Widget
618 lines (536 loc) • 20.3 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/domo-tooltip"), require("@domoinc/da-theme2"), require("@domoinc/summary-number"), require("@domoinc/utilities"), require("lodash"));
else if(typeof define === 'function' && define.amd)
define(["d3", "d3.chart", "base-widget", "domo-tooltip", "da-theme2", "summary-number", "utilities", "lodash"], factory);
else if(typeof exports === 'object')
exports["Axis"] = factory(require("d3"), require("d3.chart"), require("@domoinc/base-widget"), require("@domoinc/domo-tooltip"), require("@domoinc/da-theme2"), require("@domoinc/summary-number"), require("@domoinc/utilities"), require("lodash"));
else
root["Axis"] = factory(root["d3"], root["d3.chart"], root["BaseWidget"], root["DomoTooltip"], root["da"]["theme2"], root["SummaryNumber"], root["da"], root["_"]);
})(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__, __WEBPACK_EXTERNAL_MODULE_7__, __WEBPACK_EXTERNAL_MODULE_8__) {
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__) {
var d3 = __webpack_require__(1);
var d3Chart = __webpack_require__(2);
var BaseWidget = __webpack_require__(3);
var DomoTooltip = __webpack_require__(4);
var daTheme2 = __webpack_require__(5);
var SummaryNumber = __webpack_require__(6);
var Utilities = __webpack_require__(7);
var _ = __webpack_require__(8);
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// Axis
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
module.exports = BaseWidget.extend('Axis', {
//**********************************************************************************
// Anything in the initialization method will only run once.
// i.e. It will not be run every time you call draw.
//**********************************************************************************
initialize: function () {
'use strict';
var _Chart = this;
var _scale = d3.scale.linear();
//----------------------------------------------------------------------------------
// Default Values for the wiget's configurable options.
// Valid types for UI elements are: string, number, color, select, boolean
// NOTE: Height and Width have already been set on the base widget.
//
// Valid Properties: [Name], description, [category], type, value, [onChange]
//----------------------------------------------------------------------------------
this._newConfig = {
addBaseline: {
description: 'Adds the baseline path above the labels',
type: 'boolean',
value: false,
},
addZeroline: {
description: 'Makes the gridline corresponding to the zero tick show, and have increased stroke width',
type: 'boolean',
value: false,
},
addGridlines: {
description: 'Flag to show vertical or horizontal grid lines.',
type: 'boolean',
value: false,
},
addTicks: {
description: 'Flag to show the tick lines on the axis.',
type: 'boolean',
value: false
},
addLabels: {
description: 'Flag to show the text labels on the axis',
type: 'boolean',
value: true,
},
intelligentTicks: {
description: 'Try and show an appropriate number of ticks given available space.',
type: 'boolean',
value: true
},
intelligentTrunc: {
description: 'Truncate the labels so they do not over lap.',
type: 'boolean',
value: true
},
maxLegendSpace: {
description: '',
type: 'number',
value: 50
},
orient: {
description: 'Sets the orientation of the axis. Options: bottom, top, left, right',
type: 'string',
value: 'bottom'
},
scale: {
description: 'd3 scale object for the axis.',
type: 'scale',
value: _scale,
},
tickFormat: {
description: 'Label format function for the tick labels.',
type: 'function',
value: function (d) {
return service.summaryNumber(d);
}
},
tickPadding: {
description: 'Padding between the tick line and text.',
type: 'number',
value: 7
},
tickSize: {
description: 'Size of the tick line.',
type: 'number',
value: 5
},
tickSpacing: {
description: 'Space between each tick.',
type: 'number',
value: 40
},
axesLabelLetterSpacing: {
description: 'Letter spacing for the axes labels.',
type: 'number',
value: 0,
units: 'px'
},
x: {
description: 'The x coordinate for the axis.',
type: 'number',
value: 0
},
y: {
description: 'The y coordinate for the axis.',
type: 'number',
value: 0
},
duration: {
description: 'Duration of the animation',
value: 750,
type: 'number',
units: 'ms',
},
textFontFamily: daTheme2.themeElements.h1FontFamily({
name: 'Text Font Family',
description: '',
}),
tooltipBackgroundColor: daTheme2.themeElements.tooltipBackground(),
tooltipTextSize: {
name: 'Tooltip Text Size',
value: 14,
units: 'px',
type: 'number',
},
tooltipTextColor: daTheme2.themeElements.tooltipFontColor(),
showTooltip: {
value: false,
type: 'boolean',
},
chartName: {
description: 'Name of chart for Reporting.',
type: 'string',
value: 'Axis'
},
//THEME Elements
/*----------------------------------------------------------------------------------
//Axes (4):
----------------------------------------------------------------------------------*/
axesLineColor: daTheme2.themeElements.axesLineColor(),
axesLabelColor: daTheme2.themeElements.axesFontColor(),
axesLabelSize: daTheme2.themeElements.axesFontSize(),
axesLabelFontFamily: daTheme2.themeElements.axesFontFamily()
};
// Update config object from base chart.
this.mergeConfig(_Chart._newConfig);
//----------------------------------------------------------------------------------
// Static - Anything that is defined here will never change
//----------------------------------------------------------------------------------
var service = new SummaryNumber();
_Chart._axisBase = _Chart._layerGroup.classed('axis', true);
_Chart._axis = d3.svg.axis();
_Chart._svg = findSvg(_Chart._layerGroup.node());
function findSvg(selection) {
var selectionParent = selection.parentNode;
if (selectionParent.tagName === 'svg') {
return d3.select(selectionParent);
} else {
return findSvg(selectionParent);
}
}
var tooltip = _Chart._svg.append('g')
.classed('axisTooltip', true)
.chart('DomoTooltip')
.c('format', 'text')
.c('container', _Chart._svg);
tooltip._appends.append('text').classed('axisLabel', true);
/*----------------------------------------------------------------------------------
// Transform
----------------------------------------------------------------------------------*/
this.transform = function (data) {
updateTooltip();
verifyOreintation();
if (_Chart.c('scale') === _scale) {
setUpDefaultScale(data);
}
drawAxis();
return data;
};
//----------------------------------------------------------------------------------
// Helper Functions
//----------------------------------------------------------------------------------
/**
* Verifies orientation of the axis 'orient' config.
* Sets config to 'bottom' of not a valid config.
*/
function verifyOreintation() {
var validOptions = [
'top', // horizontal axis with ticks above the domain path
'bottom', // horizontal axis with ticks below the domain path
'left', // vertical axis with ticks to the left of the domain path
'right', // vertical axis with ticks to the right of the domain path
];
if (_.indexOf(validOptions, _Chart.c('orient')) < 0) {
console.warn('Not a valid option for orient property.' +
'Valid options are "bottom", "top", "left", "right".');
_Chart.c('orient', 'bottom');
}
}
/**
* Sets Data Scale
* @param data
*/
function setUpDefaultScale(data) {
_Chart.c('scale').domain([0, d3.max(data)]);
if (_Chart.c('orient') === 'bottom' || _Chart.c('orient') === 'top') {
_Chart.c('scale').range([0, _Chart.c('width')]);
}
else {
_Chart.c('scale').range([_Chart.c('height'), 0]);
}
}
/**
* DrawAxis
*/
function drawAxis() {
setupAxis();
_Chart._axisBase.transition().duration(_Chart.c('duration')).call(_Chart._axis);
styleAxis();
}
/**
* SetupAxis
*/
function setupAxis() {
_Chart._axisBase.attr('transform', 'translate(' + _Chart.c('x') + ',' + _Chart.c('y') + ')');
_Chart._axis
.scale(_Chart.c('scale'))
.tickFormat(_Chart.c('tickFormat'))
.tickSize(_Chart.c('tickSize'))
.tickPadding(_Chart.c('tickPadding'))
.orient(_Chart.c('orient'));
if (_Chart.c('intelligentTicks')) {
intelligentTicksNumTicks();
}
if (!_Chart.c('addTicks')) {
_Chart._axis.tickSize(0);
}
//if you don't add gridlines, tick will position and
//be sized automatically by tickSize option on _Chart._axis
if (_Chart.c('addGridlines')) {
var size = getTickSize();
_Chart._axis.innerTickSize(-size);
_Chart._axis.outerTickSize(0);
}
}
/**
* get the tick size depending if gridlines are added
* and axis orientation
*/
function getTickSize() {
var size = _Chart.c('width');
if (_Chart.c('orient') === 'bottom' || _Chart.c('orient') === 'top') {
size = _Chart.c('height');
}
if (_Chart.c('addTicks')) {
size += _Chart.c('tickSize');
}
return size;
}
/**
* get tick transform depending on axis orientation
* this function is only needed if there is ticks and gridlines
*/
function getTickTransform() {
var tickSize = _Chart.c('tickSize');
switch (_Chart.c('orient')) {
case 'top':
return 'translate(0,-' + tickSize + ')';
case 'bottom':
return 'translate(0,' + tickSize + ')';
case 'left':
return 'translate(-' + tickSize + ',0)';
case 'right':
return 'translate(' + tickSize + ',0)';
}
}
/**
* StyleAxis
*/
function styleAxis() {
_Chart._axisBase.selectAll('path')
.style({
'fill': 'none',
'stroke': _Chart.c('addBaseline') ? _Chart.c('axesLineColor') : 'none',
'stroke-width': 2,
});
_Chart._axisBase.selectAll('line')
.attr({
transform: function(){
if (_Chart.c('addTicks') && _Chart.c('addGridlines')) {
return getTickTransform();
}
}
})
.style('stroke', _Chart.c('axesLineColor'))
.each(function(d) {
// Add zeroline
var elem;
if (d === 0 && _Chart.c('addZeroline')) {
elem = d3.select(this);
elem.style('stroke-width', 2);
if (_Chart.c('addTicks')){
elem.attr('transform', getTickTransform);
}
if (_Chart.c('orient') === 'top') {
elem.transition().attr({
'y2': getTickSize(),
'x2': 0,
});
} else if (_Chart.c('orient') === 'bottom'){
elem.transition().attr({
'y2': -getTickSize(),
'x2': 0,
});
} else if (_Chart.c('orient') === 'left'){
elem.transition().attr({
'x2': getTickSize(),
'y2': 0,
});
} else if (_Chart.c('orient') === 'right'){
elem.transition().attr({
'x2': -getTickSize(),
'y2': 0,
});
}
} else if (d === 0 && !_Chart.c('addZeroline')) { // Remove zeroline
elem = d3.select(this);
elem.style('stroke-width', 1);
}
});
_Chart._axisBase.selectAll('text')
.on('mousemove', hoverMove)
.on('mouseout', hoverOff)
.style({
'fill': _Chart.c('axesLabelColor'),
'font-family': _Chart.c('axesLabelFontFamily'),
'font-weight': 400,
'font-size': _Chart.c('addLabels') ? _Chart.c('axesLabelSize') + 'px' : 0.1 + 'px',
'letter-spacing': _Chart.c('axesLabelLetterSpacing') + 'px'
});
if (_Chart.c('intelligentTicks') || !_Chart.c('intelligentTrunc')) {
intelligentTicksTruncate();
}
}
/**
* Tries to set an appropriate number of ticks given chart space.
*/
function intelligentTicksNumTicks() {
var numTicks = Math.round(getLengthOfAxis() / _Chart.c('tickSpacing'));
_Chart._axis.ticks(numTicks);
setTicksForPossibleOrdinalScale(numTicks);
}
//**********************************************************************************
// Ordinal scale do not respond to the 'ticks' function on a scale.
//**********************************************************************************
function setTicksForPossibleOrdinalScale(numTicks) {
//Most likely an ordinal scale... try tickValues instead
if (_Chart.c('scale').hasOwnProperty('rangePoints')) {
var domain = _Chart.c('scale').domain();
_Chart._axis.tickValues(domain); // ensure the tickValues are always set to the recent domain values
if (numTicks < domain.length) {
var showMod = Math.round(domain.length / numTicks);
var showTicks = _.filter(domain, function (d, i) {
return i % showMod === 0;
});
var lastDomain = domain[domain.length - 1];
var lastShown = showTicks[showTicks.length - 1];
//Last Tick Conditional: Fix the Last tick. Show one / move it to the last number in the domain.
if (domain.length - (showTicks.length * showMod) > showMod * 0.50) {
showTicks.push(lastDomain);
}
else {
showTicks[showTicks.length - 1] = lastDomain;
}
_Chart._axis.tickValues(showTicks);
}
}
}
/**
* Truncates tick labels to that they don't overlay.
*/
function intelligentTicksTruncate() {
var unattachedAxis = d3.select(document.createElement('g')).call(_Chart._axis);
var unattachedTextCnt = unattachedAxis.selectAll('text').size();
var orient = _Chart.c('orient');
var trueSpacing = orient === 'left' || orient === 'right' ?
_Chart.c('maxLegendSpace') : Math.round(getLengthOfAxis() / unattachedTextCnt);
_Chart._axisBase.selectAll('text')
.each(function (d) {
d3.domoStrings.truncToFit(d3.select(this), trueSpacing);
});
}
/**
* Returns length of axis given the current orientation.
*/
function getLengthOfAxis() {
var orient = _Chart.c('orient');
return orient === 'bottom' || orient === 'top' ? _Chart.c('width') : _Chart.c('height');
}
/**
* hover move
*/
function hoverMove(d) {
if (_Chart.c('showTooltip')) {
var valueIsString = !parseFloat(d); //if it's a string, and not a string number
//if the string data is different from the text
var dataIsDifferentFromText = String(d) !== d3.select(this).text(); // jshint ignore:line
var labelIsTruncated = valueIsString && dataIsDifferentFromText;
if (labelIsTruncated) {
var coordinates = d3.mouse(_Chart._svg.node());
var x = coordinates[0];
var y = coordinates[1];
var point = {
x: x,
y: y,
};
tooltip._appends.select('text').text(function() { return _Chart.c('tickFormat')(d); })
tooltip.trigger('draw');
tooltip.trigger('moveTo', point)
}
}
}
/**
* hover off
*/
function hoverOff(d) {
if (_Chart.c('showTooltip')) {
tooltip.trigger('remove');
}
}
/**
* update tooltip
*/
function updateTooltip() {
tooltip._appends.selectAll('text')
.style({
'font-family': _Chart.c('textFontFamily'),
'font-size': _Chart.c('tooltipTextSize') + 'px',
'fill': _Chart.c('tooltipTextColor'),
});
tooltip._appends.select('rect').style('fill', _Chart.c('tooltipTextColor'));
tooltip.c('tooltipBackgroundColor', _Chart.c('tooltipBackgroundColor'))
}
}
});
/***/ },
/* 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__;
/***/ },
/* 7 */
/***/ function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_7__;
/***/ },
/* 8 */
/***/ function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_8__;
/***/ }
/******/ ])
});
;