d2b
Version:
1,160 lines (971 loc) • 240 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var d3Selection = require('d3-selection');
var d3Interpolate = require('d3-interpolate');
require('d3-transition');
var d3Dispatch = require('d3-dispatch');
var d3Array = require('d3-array');
var d3Collection = require('d3-collection');
var d3Shape = require('d3-shape');
var d3Scales = require('d3-scale');
var d3ScaleChromatic = require('d3-scale-chromatic');
var d3Color = require('d3-color');
var d3SvgAnnotation = require('d3-svg-annotation');
var d3InterpolatePath = require('d3-interpolate-path');
var d3Format = require('d3-format');
var d3Hierarchy = require('d3-hierarchy');
var d3Sankey = require('d3-sankey');
var d3Drag = require('d3-drag');
var d3Axis = require('d3-axis');
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css = ".d2b-draggable{cursor:move}.d2b-chart-advanced{width:100%;height:100%}.d2b-pie-chart .d2b-pie-arc path{stroke-width:1px;stroke:#fff}.d2b-pie-chart .d2b-pie-arc text{fill:#fff;font-weight:700;pointer-events:none;text-anchor:middle}.d2b-axis-wrapper .d2b-axis-background{opacity:0}.d2b-chart-breadcrumbs{width:200px;padding-left:10px}.d2b-sunburst-breadcrumb .d2b-sunburst-label,.d2b-sunburst-tooltip .d2b-sunburst-label{text-align:center}.d2b-sunburst-breadcrumb .d2b-sunburst-value,.d2b-sunburst-tooltip .d2b-sunburst-value{font-size:14pt;margin-top:5px}.d2b-sunburst-breadcrumb .d2b-sunburst-percent{float:right}.d2b-breadcrumbs:not(.d2b-vertical) .d2b-sunburst-percent{margin-left:30px}.d2b-sunburst-tooltip{text-align:center}.d2b-sunburst-tooltip .d2b-sunburst-percent{display:inline}.d2b-sunburst-tooltip .d2b-sunburst-percent:before{content:\"(\"}.d2b-sunburst-tooltip .d2b-sunburst-percent:after{content:\")\"}.d2b-sankey-link-arrow{font-size:10px;position:relative;top:-2px}.d2b-line-graph .d2b-line{stroke-width:1.5px;fill:none}.d2b-area-graph .d2b-area{stroke:none;fill-opacity:.3}.d2b-box .d2b-box-center,.d2b-box .d2b-box-dash,.d2b-box .d2b-box-outlier,.d2b-box .d2b-box-rect{stroke-width:1.5px}.d2b-box .d2b-box-rect{fill:#fff;fill-opacity:.3}.d2b-box .d2b-box-center{stroke-dasharray:3 3}.d2b-box .d2b-box-label{fill:#555;font-size:10pt}.d2b-box .d2b-box-outlier{fill:none;stroke-opacity:.4}.d2b-bar-graph .d2b-bar-group rect{opacity:.8}.d2b-bubble-pack-graph .d2b-bubble-point{opacity:.75}.d2b-bubble-pack-graph .d2b-bubble-indicator rect{fill-opacity:.25;stroke-opacity:.9;cursor:pointer;stroke-width:1px}.d2b-bubble-pack-graph .d2b-bubble-indicator rect:hover{stroke-width:2px}.d2b-bubble-pack-graph .d2b-bubble-indicator path,.d2b-bubble-pack-graph .d2b-bubble-indicator text{pointer-events:none}.d2b-plane text{font-size:10pt}.d2b-plane .d2b-y2-axis .d2b-axis-label,.d2b-plane .d2b-y-axis .d2b-axis-label{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.d2b-plane .d2b-y2-axis .tick line,.d2b-plane .d2b-y-axis .tick line{stroke-width:.6px}.d2b-plane .d2b-grid .tick line{shape-rendering:crispEdges;stroke-width:.5px;stroke-opacity:.15}.d2b-plane .d2b-grid .tick text{display:none}.d2b-plane .d2b-axis-label{fill:#000;font-weight:700}.d2b-plane path.domain{stroke-width:.4px;stroke-opacity:.4}.d2b-sunburst-arc{transition:opacity .2s,stroke-width .2s;cursor:pointer;opacity:.9;stroke-width:.8px;stroke:#fff}.d2b-sunburst-arc.d2b-transparent{fill-opacity:.2}.d2b-sunburst-label{font-size:8pt;fill-opacity:.8;pointer-events:none;font-family:arial}.d2b-sunburst-label.d2b-transparent{fill-opacity:.2}.d2b-sunburst-center{fill-opacity:0;stroke:none;cursor:pointer}.d2b-sunburst-ancestor{opacity:.4}.d2b-sankey-links path{fill:none}.d2b-sankey-links path,.d2b-sankey-links rect{opacity:.4}.d2b-sankey-links path:hover,.d2b-sankey-links rect:hover{opacity:.6}.d2b-sankey-nodes rect{stroke-width:.3px;stroke:#000;opacity:.6}.d2b-sankey-nodes rect:hover{opacity:.8}.d2b-text-anchor-end{text-anchor:end}.d2b-tooltip{pointer-events:none;background:#fff;border:1px solid #bbb;border-radius:2px;position:fixed;box-shadow:0 0 2px #ccc;margin:0}.d2b-tooltip:before{left:0;top:0;position:absolute;z-index:2;content:\"\"}.d2b-tooltip-content{white-space:nowrap;padding:6px 10px}.d2b-tooltip-top{-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%);margin-left:0;margin-top:-15px}.d2b-tooltip-top:before{-webkit-transform:translateX(-50%);transform:translateX(-50%);left:50%;top:100%;border-color:transparent;border-style:solid;border-width:6px 7px 0;border-top-color:inherit}.d2b-tooltip-bottom{-webkit-transform:translate(-50%);transform:translate(-50%);margin-left:0;margin-top:15px}.d2b-tooltip-bottom:before{-webkit-transform:translateX(-50%);transform:translateX(-50%);left:50%;top:-6px;border-color:transparent;border-style:solid;border-width:0 7px 6px;border-bottom-color:inherit}.d2b-tooltip-right{margin-left:15px;margin-top:0}.d2b-tooltip-right,.d2b-tooltip-right:before{-webkit-transform:translateY(-50%);transform:translateY(-50%)}.d2b-tooltip-right:before{left:-6px;top:50%;border-color:transparent;border-style:solid;border-width:7px 6px 7px 0;border-right-color:inherit}.d2b-tooltip-left{-webkit-transform:translate(-100%,-50%);transform:translate(-100%,-50%);margin-left:-15px;margin-top:0}.d2b-tooltip-left:before{-webkit-transform:translateY(-50%);transform:translateY(-50%);left:100%;top:50%;border-color:transparent;border-style:solid;border-width:7px 0 7px 6px;border-left-color:inherit}.d2b-tooltip-marker{stroke-width:2px;stroke:#bbb;stroke-dasharray:5,5}.d2b-tooltip-axis{pointer-events:none;font-family:Arial;opacity:.9;white-space:nowrap;border:1px solid #ddd;background:hsla(0,0%,100%,.8);position:absolute;box-shadow:0 0 2px #ccc}.d2b-tooltip-axis .d2b-tooltip-title{text-align:center;background:rgba(150,165,175,.8);padding:5px 10px;font-weight:700;color:#fff}.d2b-tooltip-axis .d2b-tooltip-content{padding:5px 10px}.d2b-tooltip-axis .d2b-tooltip-content .d2b-tooltip-row{padding-left:5px;border-left-width:3px;border-left-style:solid;margin-bottom:5px}.d2b-tooltip-axis .d2b-tooltip-content .d2b-tooltip-row:last-child{margin-bottom:0}.d2b-breadcrumbs{color:#555;font-size:9pt;box-sizing:content-box}.d2b-breadcrumbs .d2b-breadcrumb{border-color:transparent;border-style:solid;border-width:0 0 1px 8px;padding:6px 13px;margin-bottom:10px;text-transform:uppercase;position:relative}.d2b-breadcrumbs .d2b-breadcrumb:first-child .d2b-breadcrumb-icon:after,.d2b-breadcrumbs .d2b-breadcrumb:last-child .d2b-breadcrumb-icon:after{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);left:-15.5px;width:13px;height:13px;text-align:center;font-family:FontAwesome,Font Awesome\\ 5 Free;font-weight:700;color:#fff;background-color:inherit;padding:5px;border-radius:15px}.d2b-breadcrumbs .d2b-breadcrumb:first-child .d2b-breadcrumb-icon:after{content:\"\\f015\"}.d2b-breadcrumbs .d2b-breadcrumb:not(:first-child):last-child .d2b-breadcrumb-icon:after{content:\"\\f25a\"}.d2b-breadcrumbs:not(.d2b-vertical){white-space:nowrap}.d2b-breadcrumbs:not(.d2b-vertical) .d2b-breadcrumb{border-right-width:1px;border-top-width:1px;white-space:nowrap;display:inline-block}.d2b-breadcrumbs:not(.d2b-vertical) .d2b-breadcrumb:not(:last-child){margin-right:20px}.d2b-breadcrumbs:not(.d2b-vertical) .d2b-breadcrumb:not(:last-child):after{border-color:transparent;border-style:solid;border-width:7px 0 7px 6px;border-left-color:inherit;position:absolute;z-index:2;content:\"\";top:50%;left:100%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.d2b-breadcrumbs.d2b-vertical .d2b-breadcrumb:not(:last-child):after{border-color:transparent;border-style:solid;border-width:6px 7px 0;border-top-color:inherit;height:6px;background-color:inherit;position:absolute;z-index:2;content:\"\";top:100%;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.d2b-legend{color:#555;font-size:9pt}.d2b-legend .d2b-legend-item{border:0 solid transparent;padding:2px 2px 2px 18px;position:relative;cursor:pointer}.d2b-legend .d2b-legend-item .d2b-legend-icon{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);left:7px;width:12px;height:12px}.d2b-legend .d2b-legend-item .d2b-legend-svg-icon{margin-left:-5px;margin-top:-5px;pointer-events:none}.d2b-legend .d2b-legend-item .d2b-legend-svg-icon path,.d2b-legend .d2b-legend-item .d2b-legend-svg-icon text{font-family:FontAwesome,Font Awesome\\ 5 Free;font-weight:700;text-anchor:middle;stroke-width:1px;fill-opacity:.8}.d2b-legend:not(.d2b-vertical) .d2b-legend-item{display:inline-block}.d2b-legend:not(.d2b-vertical) .d2b-legend-item:not(:last-child){margin-right:10px}.d2b-legend:not(.d2b-vertical) .d2b-legend-icon{margin-top:1.5px}.d2b-chart-frame{height:100%;width:100%;position:relative}.d2b-chart-frame .d2b-breadcrumbs-frame,.d2b-chart-frame .d2b-chart,.d2b-chart-frame .d2b-legend-frame{position:absolute;overflow:auto}.d2b-chart-frame .d2b-breadcrumbs-frame .d2b-breadcrumbs,.d2b-chart-frame .d2b-legend-frame .d2b-breadcrumbs{padding-left:10px;padding-right:2px}.d2b-chart-frame .d2b-breadcrumbs-frame:not(.d2b-vertical),.d2b-chart-frame .d2b-legend-frame:not(.d2b-vertical){overflow-y:hidden}.d2b-chart-frame .d2b-breadcrumbs-frame:not(.d2b-vertical).d2b-legend-frame,.d2b-chart-frame .d2b-legend-frame:not(.d2b-vertical).d2b-legend-frame{height:20px;text-align:center}.d2b-chart-frame .d2b-breadcrumbs-frame:not(.d2b-vertical).d2b-breadcrumbs-frame,.d2b-chart-frame .d2b-legend-frame:not(.d2b-vertical).d2b-breadcrumbs-frame{height:55px}.d2b-chart-frame .d2b-breadcrumbs-frame:not(.d2b-vertical) .d2b-breadcrumbs,.d2b-chart-frame .d2b-breadcrumbs-frame:not(.d2b-vertical) .d2b-legend,.d2b-chart-frame .d2b-breadcrumbs-frame:not(.d2b-vertical) .d2b-legend-item,.d2b-chart-frame .d2b-legend-frame:not(.d2b-vertical) .d2b-breadcrumbs,.d2b-chart-frame .d2b-legend-frame:not(.d2b-vertical) .d2b-legend,.d2b-chart-frame .d2b-legend-frame:not(.d2b-vertical) .d2b-legend-item{white-space:nowrap;overflow-y:hidden}.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical.d2b-breadcrumbs-frame,.d2b-chart-frame .d2b-legend-frame.d2b-vertical.d2b-breadcrumbs-frame{width:180px}.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical.d2b-legend-frame,.d2b-chart-frame .d2b-legend-frame.d2b-vertical.d2b-legend-frame{width:110px}.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical .d2b-breadcrumbs-container,.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical .d2b-legend-container,.d2b-chart-frame .d2b-legend-frame.d2b-vertical .d2b-breadcrumbs-container,.d2b-chart-frame .d2b-legend-frame.d2b-vertical .d2b-legend-container{display:table;height:100%;width:100%}.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical .d2b-breadcrumbs-container .d2b-legend,.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical .d2b-legend-container .d2b-legend,.d2b-chart-frame .d2b-legend-frame.d2b-vertical .d2b-breadcrumbs-container .d2b-legend,.d2b-chart-frame .d2b-legend-frame.d2b-vertical .d2b-legend-container .d2b-legend{display:table-cell;vertical-align:middle}.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical .d2b-breadcrumbs-container .d2b-breadcrumbs,.d2b-chart-frame .d2b-breadcrumbs-frame.d2b-vertical .d2b-legend-container .d2b-breadcrumbs,.d2b-chart-frame .d2b-legend-frame.d2b-vertical .d2b-breadcrumbs-container .d2b-breadcrumbs,.d2b-chart-frame .d2b-legend-frame.d2b-vertical .d2b-legend-container .d2b-breadcrumbs{display:table-cell;vertical-align:top}";
styleInject(css);
var version = "1.0.10";
function functor(v) {
return typeof v === 'function' ? v : function () {
return v;
};
}
// Work around for JavaScripts ||= operator. Only null, undefined, NaN, and false will be construed as falsy.
function oreq () {
var truthy = function truthy(v) {
return v !== null && v !== undefined && v !== false;
};
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var val = args.filter(truthy)[0];
return truthy(val) ? val : undefined;
}
// d3's enter/update/exit strategy as to be less destructive on the text content.
function textWrap (text) {
var getText = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (d) {
return d.label;
};
var getCount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Infinity;
var getAnchor = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'start';
getText = functor(getText);
getCount = functor(getCount);
getAnchor = functor(getAnchor);
text.each(function (d, i) {
var text = d3Selection.select(this),
words = "".concat(getText.call(this, d, i)).split(/\s+/).reverse(),
word,
lines = [],
line = [words.pop()],
lineHeight = 1.1,
count = getCount.call(this, d, i),
anchor = getAnchor.call(this, d, i),
x = +text.attr('x'),
y = +text.attr('y'),
dy = parseFloat(text.attr('dy')) || 0; // clear text if the wrapper is being run for the first time
if (oreq(text.html(), '').indexOf('tspan') === -1) text.text('');
word = words.pop();
while (word) {
if (line.join(' ').length + word.length > count) {
lines.push(line);
line = [];
}
line.push(word);
word = words.pop();
}
lines.push(line);
var tspan = text.selectAll('tspan').data(lines),
height = (lines.length - 1) * lineHeight,
offset = anchor === 'end' ? height : anchor === 'middle' ? height / 2 : 0;
tspan.merge(tspan.enter().append('tspan')).text(function (d) {
return d.join(' ');
}).attr('x', x).attr('y', y).attr('dy', function (d, i) {
return "".concat(dy + i * lineHeight - offset, "em");
});
});
}
// This isn't used very frequently because it causes problems with event
// rebinding namely double click events.
function textWrapPX (text) {
var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Infinity;
text.each(function () {
var text = d3Selection.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1,
// ems
y = parseFloat(text.attr('y')) || 0,
dy = parseFloat(text.attr('dy')) || 0,
tspan = text.text(null).append('tspan').attr('x', 0).attr('y', y).attr('dy', dy + 'em');
word = words.pop();
while (word) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = text.append('tspan').attr('x', 0).attr('y', y).attr('dy', ++lineNumber * lineHeight + dy + 'em').text(word);
}
word = words.pop();
}
});
}
function tweenArc (context, arc) {
var reform = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
// Reform the arc config methods so that they will first look at the arc datum
// for the config property and otherwise they will fallback to the arcs methods
// original callback
if (reform) {
['cornerRadius', 'innerRadius', 'outerRadius', 'startAngle', 'endAngle', 'cornerRadius', 'padAngle'].forEach(function (method) {
var methodSave = arc[method]();
if (methodSave && !methodSave.reformed) {
var methodReformed = function methodReformed(d) {
return d[method] || methodSave(d);
};
methodReformed.reformed = true;
arc[method](methodReformed);
}
});
}
function getProperties(d) {
return {
innerRadius: arc.innerRadius()(d),
outerRadius: arc.outerRadius()(d),
startAngle: arc.startAngle()(d),
endAngle: arc.endAngle()(d),
cornerRadius: arc.cornerRadius()(d),
padAngle: arc.padAngle()(d)
};
} // if context is not a transition just render the arc and update current
if (!context.selection) {
return context.attr('d', function (d) {
this.current = getProperties(d);
return arc(d);
});
} // if context is a transition tween the 'd' attribute
context.attrTween('d', function (d) {
var _this = this;
// omit data attribute incase of a pie chart with nested associations
d = getProperties(d);
this.current = this.current || d;
var i = d3Interpolate.interpolate(this.current, d);
return function (t) {
_this.current = i(t);
return arc(_this.current);
};
});
}
function numberize (x) {
var def = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (isNaN(x) || x === null) return def;
return x;
}
function tweenNumber (context) {
var number = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (d) {
return d;
};
var format = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (d) {
return d;
};
number = functor(number); // if context is not a transition just render the text and update current
if (!context.selection) {
return context.text(function (d, i) {
this.current = numberize(number.call(this, d, i));
return format(this.current);
});
}
context.tween('text', function (d, i) {
var _this = this;
var val = numberize(number.call(this, d, i));
this.current = numberize(this.current, val);
var interp = d3Interpolate.interpolate(this.current, val);
return function (t) {
_this.textContent = format(_this.current = interp(t));
};
});
}
function tweenCentroid (context, arc) {
var rotate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (d) {
return d.rotate;
};
rotate = functor(rotate); // get specific set of properties in case of recursive
function getProperties(d) {
return {
innerRadius: arc.innerRadius()(d),
outerRadius: arc.outerRadius()(d),
startAngle: arc.startAngle()(d),
endAngle: arc.endAngle()(d),
rotate: rotate(d) || 0
};
} // if context is not a transition just render the centroid and update current
if (!context.selection) {
return context.attr('transform', function (d) {
this.current = getProperties(d);
return "translate(".concat(arc.centroid(this.current), ")") + "rotate(".concat(this.current.rotate, ")");
});
}
context.attrTween('transform', function (d) {
var _this = this;
d = getProperties(d);
this.current = this.current || d;
var i = d3Interpolate.interpolate(this.current, d);
return function (t) {
_this.current = i(t);
return "translate(".concat(arc.centroid(_this.current), ") ") + "rotate(".concat(_this.current.rotate || 0, ")");
};
});
}
/**
* d2b.modelBase() returns a d2b base model.
*
* model.interface() will return a base interface with various built in
* getter/setter methods.
* model.values() will return the values set through the interface.
* @param {function} base - function that will act as the model interface
* @param {object} $$ - attributes set by interactive with the base interface
* @return {Object} model - object with properties and methods
*/
function base () {
var _this = this;
var _base = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var $$ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// Define an emitter updater that will fire events around the base updater
// if (typeof base === 'function') {
// base.emitter = function (context) {
// if (context.dispatch) context.dispatch('beforeApply', {bubbles: true});
// base.apply(this, arguments);
// if (context.dispatch) context.dispatch('applied', {bubbles: true});
// };
// }
var propFn = function propFn(prop, cb) {
return function (_) {
if (!arguments.length) return $$[prop];
var old = $$[prop];
$$[prop] = _;
if (cb) cb(_, old);
return _base;
};
};
var propFnGet = function propFnGet(prop) {
return function () {
return $$[prop];
};
};
var propFnFunctor = function propFnFunctor(prop, cb) {
return function (_) {
if (!arguments.length) return $$[prop];
var old = $$[prop];
$$[prop] = functor(_);
if (cb) cb($$[prop], old);
return _base;
};
};
var scaleFnFunctor = function scaleFnFunctor(prop, cb) {
return function (_) {
if (!arguments.length) return $$[prop];
var old = $$[prop];
if (_ && _.domain) $$[prop] = function () {
return _;
};else $$[prop] = functor(_);
if (cb) cb($$[prop], old);
return _base;
};
}; // Conditionally call a prop with arguments
// If there are more than 2 arguments supplied, the prop will be called
// with the arguments as long as the condition is truthy
// Else if only 2 arguments suplied, the prop will be called with the
// condition argument as long as the condition is defined
_base.conditionally = function (prop, cond) {
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
if (args.length) {
if (cond || cond === 0) _base[prop].apply(null, args);
} else {
if (cond !== undefined) _base[prop](cond);
}
return _base;
};
var methodAdded = function methodAdded(method) {
// Adds the conditionally modifier to a newly created base method. Similar to base.conditionally,
// but is added to the method itself. e.g. `base.method.conditionally()`
method.conditionally = function (cond) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
if (args.length) {
if (cond || cond === 0) method.apply(null, args);
} else {
if (cond !== undefined) method(cond);
}
return _base;
};
};
/* Base Model */
var model = {
base: function base() {
return _base;
},
values: function values() {
return $$;
},
/**
* model.removeProp removes the specified property
* @param {Number} prop - property key
* @return {Object} model - returns model to allow for method chaining
*/
removeProp: function removeProp(prop) {
$$[prop] = null;
_base[prop] = null;
return model;
},
/**
* model.addProp allows new properties to be added to the model and base
* interface. If the property is already defined an error will be raised.
* @param {Number} prop - property key
* @param {Number} value - default value to set
* @param {Number} fn - function as new prop getter/setter
* @param {Number} cb - callback function after prop is set
* @return {Object} model - returns model to allow for method chaining
*/
addProp: function addProp(prop) {
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var fn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : propFn(prop);
var cb = arguments.length > 3 ? arguments[3] : undefined;
if ($$[prop] || _base[prop]) {
// console.error(`${prop} property is already defined.`);
return model;
} // allow for null:default 'fn' in order to access callback
fn = fn || propFn(prop, cb);
fn(value);
_base[prop] = fn;
methodAdded(_base[prop]);
return model;
},
/**
* model.addPropGet is similar to addProp except it doesn't allow for the
* property to be reset through the API.
* @param {Number} prop - property key
* @param {Number} value - default value to set
* @param {Number} fn - function as new prop getter
* @return {Object} model - returns model to allow for method chaining
*/
addPropGet: function addPropGet(prop) {
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var fn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : propFnGet(prop);
if ($$[prop] || _base[prop]) {
// console.error(`${prop} property is already defined.`);
return model;
}
$$[prop] = value;
_base[prop] = fn;
methodAdded(_base[prop]);
return model;
},
/**
* model.addMethod allows new methods to be added to the model and base
* interface. If the method is already defined an error will be raised.
* @param {Number} method - method key
* @param {Number} fn - new method
* @return {Object} model - returns model to allow for method chaining
*/
addMethod: function addMethod(method, fn) {
if (_base[method]) {
// console.error(`${method} method is already defined.`);
return model;
}
_base[method] = fn;
methodAdded(_base[method]);
return model;
},
/**
* model.addPropFunctor allows new functor properties to be added to the
* model and base interface. If the property is already defined an error
* will be raised.
* @param {Number} prop - property key
* @param {Number} value - default value to set
* @param {Number} fn - function as new prop getter/setter
* @return {Object} model - returns model to allow for method chaining
*/
addPropFunctor: function addPropFunctor(prop) {
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var fn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : propFnFunctor(prop);
var cb = arguments.length > 3 ? arguments[3] : undefined;
if ($$[prop] || _base[prop]) {
// console.error(`${prop} property is already defined.`);
return model;
} // allow for null:default 'fn' in order to access callback
fn = fn || propFnFunctor(prop, cb);
fn(value);
var newProp = _base[prop] = fn;
methodAdded(_base[prop]);
newProp.proxy = function (proxy) {
var current = newProp();
var original = current.original || current;
var proxied = function proxied() {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
var value = proxy.apply(this, args);
return value === undefined ? original.apply(this, args) : value;
};
proxied.original = original;
newProp(proxied);
return _base;
};
return model;
},
/**
* model.addScaleFunctor allows new scale functor properties to be added
* to the model and base interface. If the property is already defined
* an error will be raised.
* @param {Number} prop - property key
* @param {Number} value - default value to set
* @param {Number} fn - function as new prop getter/setter
* @return {Object} model - returns model to allow for method chaining
*/
addScaleFunctor: function addScaleFunctor(prop) {
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var fn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : scaleFnFunctor(prop);
var cb = arguments.length > 3 ? arguments[3] : undefined;
return model.addProp(prop, value, fn, cb);
},
/**
* model.addDispatch allows dispatcher to be added to the model and base
* interface.
* @param {Number} prop - property key
* @param {Number} store - store key
* @param {Number} events - array of event keys
* @return {Object} model - returns model to allow for method chaining
*/
addDispatcher: function addDispatcher(events) {
var prop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'on';
var store = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'dispatch';
if (_base[prop]) {
// console.error(`${prop} property is already defined.`);
return model;
}
if ($$[store]) {
// console.error(`${store} value is already defined.`);
return model;
}
_base[prop] = function (key, fn) {
if (arguments.length === 0) return $$[store];
if (arguments.length === 1) return $$[store].on(key);
$$[store].on(key, fn);
return _base;
};
methodAdded(_base[prop]);
$$[store] = d3Dispatch.dispatch.apply(_this, events);
return model;
},
/**
* model.addAdvancedConfig adds the base.advanced prop that will proxy through
* some config function before rendering the base on the selection.
* @param {Number} conf - property key
* @return {Object} model - returns model to allow for method chaining
*/
addAdvancedConfig: function addAdvancedConfig(config) {
_base.advanced = function (context) {
var selection = context.selection ? context.selection() : context;
var transition = selection !== context;
selection.each(function (datum) {
if (datum.updated !== undefined) d3Selection.select(this).on('chart-updated.advanced', datum.updated);
var el = d3Selection.select(this).selectAll('.d2b-chart-advanced').data([config(_base, datum)]);
var elEnter = el.enter().append('div').attr('class', 'd2b-chart-advanced');
el = elEnter.merge(el);
var elTransition = transition ? el.transition(context) : el;
elTransition.call(_base);
});
return _base;
};
return model;
}
};
return model;
}
var isTouchDevice = function isTouchDevice() {
return 'ontouchstart' in window || window.navigator.msPointerEnabled;
};
function tooltip () {
var $$ = {};
var tooltip = function tooltip(context) {
var selection = context.selection ? context.selection() : context;
selection.on(event('mouseover'), mouseover).on(event('mouseout'), mouseout);
if (isTouchDevice()) {
selection.on(event('click'), mouseout);
} else {
selection.on(event('mousemove'), mousemove);
}
return tooltip;
};
var getCoords = function getCoords(d, i) {
var box = this.getBoundingClientRect();
var coords = {}; // construct at object, if null automatically set it based on cursor event position
var at = ($$.at.call(this, d, i) || (d3Selection.event.clientX > window.innerWidth / 2 ? 'center left' : 'center right')).split(' ');
at = {
x: at[1],
y: at[0]
}; // switch for horizontal coordinate
switch (at.x) {
case 'left':
coords.x = box.left;
break;
case 'center':
coords.x = box.left + box.width / 2;
break;
default:
// right
coords.x = box.left + box.width;
} // switch for vertical coordinate
switch (at.y) {
case 'bottom':
coords.y = box.top + box.height;
break;
case 'center':
coords.y = box.top + box.height / 2;
break;
default:
// top
coords.y = box.top;
}
return coords;
};
var mouseover = function mouseover(d, i) {
var html = $$.html.call(this, d, i);
if (!html) return;
var tooltipUpdate = $$.container.selectAll('.d2b-tooltip').data(function (d) {
return [d];
});
var newTooltip = tooltipUpdate.enter().append('div').style('opacity', 0).attr('class', 'd2b-tooltip');
newTooltip.append('div').attr('class', 'd2b-tooltip-content');
tooltipUpdate = tooltipUpdate.merge(newTooltip);
tooltipUpdate.transition().duration(100).style('opacity', 1);
$$.dispatch.call('insert', tooltipUpdate, this, d, i);
mousemove.call(this, d, i);
};
var mousemove = function mousemove(d, i) {
var html = $$.html.call(this, d, i),
target = $$.target.call(this, d, i),
color = $$.color.call(this, d, i),
targetNode = target ? target.node() : this,
coords = $$.followMouse.call(this, d, i) ? {
x: d3Selection.event.clientX,
y: d3Selection.event.clientY
} : getCoords.call(targetNode, d, i);
if (!html) return; // if (!$$.container.selectAll('.d2b-tooltip').size()) return mouseover(d, i);
var tooltipUpdate = $$.container.selectAll('.d2b-tooltip').data(function (d) {
return [d];
});
var my = $$.my.call(this, d, i) || (d3Selection.event.clientX > window.innerWidth / 2 ? 'left' : 'right');
tooltipUpdate.attr('class', "d2b-tooltip d2b-tooltip-".concat(my)).style('top', coords.y + 'px').style('left', coords.x + 'px').style('border-color', color).select('.d2b-tooltip-content').html(html);
$$.dispatch.call('move', tooltipUpdate, this, d, i);
};
var mouseout = function mouseout(d, i) {
var tooltipUpdate = $$.container.selectAll('.d2b-tooltip').data(function (d) {
return [d];
});
tooltipUpdate.transition().duration(100).style('opacity', 0).remove();
$$.dispatch.call('remove', tooltipUpdate, this, d, i);
};
var event = function event(listener) {
return "".concat(listener, ".d2b-tooltip");
};
/* Inherit from base model */
base(tooltip, $$).addProp('container', d3Selection.select('body')) //, null, updateContainer)
.addMethod('clear', function (context) {
(context.selection ? context.selection() : context).on(event('mouseover'), null).on(event('mouseout'), null).on(event('mousemove'), null);
return tooltip;
}).addPropFunctor('followMouse', false).addPropFunctor('color', null).addPropFunctor('my', null).addPropFunctor('at', null).addPropFunctor('target', null).addPropFunctor('html', null).addDispatcher(['insert', 'move', 'remove']);
return tooltip;
}
// Work around for JavaScripts ||= operator. Only undefined will be construed as falsy.
function oreqUndefined () {
var truthy = function truthy(v) {
return v !== undefined;
};
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var val = args.filter(truthy)[0];
return truthy(val) ? val : undefined;
}
function tooltipAxis () {
var $$ = {};
var tooltip = {}; // Position markers relative to selected points and axes
var positionMarker = function positionMarker(marker, info, type) {
if (type === 'y') {
if (info.y === Infinity) return marker.style('opacity', 0);
marker.style('opacity', 1).attr('transform', "translate(0, ".concat(info.y, ")")).attr('y1', 0).attr('y2', 0).attr('x1', 0).attr('x2', $$.size.width);
} else {
if (info.x === Infinity) return marker.style('opacity', 0);
marker.style('opacity', 1).attr('transform', "translate(".concat(info.x, ", 0)")).attr('x1', 0).attr('x2', 0).attr('y1', 0).attr('y2', $$.size.height);
}
}; // Position tooltip relative to selected points and axes
var positionTooltip = function positionTooltip(tooltip, info, base) {
var node = tooltip.node();
if (!node) return;
var tooltipBox = tooltip.node().getBoundingClientRect();
var x = base.x,
y = base.y,
pad = 10;
if ($$.trackY) {
if (info.y > $$.size.height / 2) {
y += info.y - pad - tooltipBox.height;
} else {
y += info.y + pad;
}
} else {
if (d3Selection.event.clientY - base.y > $$.size.height / 2) {
y = d3Selection.event.clientY - pad - tooltipBox.height;
} else {
y = d3Selection.event.clientY + pad;
}
}
if ($$.trackX) {
if (info.x > $$.size.width / 2) {
x += info.x - pad - tooltipBox.width;
} else {
x += info.x + pad;
}
} else {
if (d3Selection.event.clientX - base.x > $$.size.width / 2) {
x = d3Selection.event.clientX - pad - tooltipBox.width;
} else {
x = d3Selection.event.clientX + pad;
}
}
x += window.pageXOffset;
y += window.pageYOffset;
tooltip.style('left', x + 'px').style('top', y + 'px');
}; // Populate tooltip with point rows
var populateTooltip = function populateTooltip(tooltip, info) {
var title = $$.title(info.points.map(function (d) {
return d.data;
}));
tooltip.select('.d2b-tooltip-title').style('display', title ? 'block' : 'none').html(title);
var content = tooltip.select('.d2b-tooltip-content');
var row = content.selectAll('.d2b-tooltip-row').data(info.points);
var rowEnter = row.enter().append('div').attr('class', 'd2b-tooltip-row');
row.exit().remove();
row = row.merge(rowEnter).html(function (d) {
return d.row;
}).style('border-left-color', function (d) {
return d.color || 'transparent';
});
}; // Finds the x, y coordinates associated with the points 'closest' to the cursor.
// Also returns the set of points that meet the 'closest' configuration.
var findPointInfo = function findPointInfo(base) {
var cursor = {
x: d3Selection.event.clientX - base.x,
y: d3Selection.event.clientY - base.y
};
var x = Infinity,
y = Infinity,
points = [];
for (var groupName in groups) {
if (!groups.hasOwnProperty(groupName)) continue;
var group = groups[groupName];
var _loop = function _loop(graphName) {
if (!group.hasOwnProperty(graphName)) return "continue";
var graph = group[graphName];
var newPoints = [];
graph.config.data.forEach(function (d, i) {
var item = {
data: d,
x: oreq(graph.config.x(d, i), $$.x(d, i)),
y: oreq(graph.config.y(d, i), $$.y(d, i)),
color: oreq(graph.config.color(d, i), $$.color(d, i)),
row: oreqUndefined(graph.config.row(d, i), $$.row(d, i))
};
if ($$.trackX && $$.trackY) {
if (item.x === x && item.y === y) return newPoints.push(item);
var od = Math.sqrt(Math.pow(x - cursor.x, 2) + Math.pow(y - cursor.y, 2));
var nd = Math.sqrt(Math.pow(item.x - cursor.x, 2) + Math.pow(item.y - cursor.y, 2));
if (nd < od && nd < $$.threshold) {
x = item.x;
y = item.y;
points = [];
newPoints = [item];
}
} else if ($$.trackX) {
if (item.x === x) return newPoints.push(item);
var _od = Math.abs(x - cursor.x);
var _nd = Math.abs(item.x - cursor.x);
if (_nd < _od && _nd < $$.threshold) {
x = item.x;
points = [];
newPoints = [item];
}
} else if ($$.trackY) {
if (item.y === y) return newPoints.push(item);
var _od2 = Math.abs(y - cursor.y);
var _nd2 = Math.abs(item.y - cursor.y);
if (_nd2 < _od2 && _nd2 < $$.threshold) {
y = item.y;
points = [];
newPoints = [item];
}
}
});
points = points.concat(newPoints);
};
for (var graphName in group) {
var _ret = _loop(graphName);
if (_ret === "continue") continue;
}
}
points = points.sort(function (a, b) {
return d3Array.ascending(a.x, b.x) || d3Array.ascending(a.y, b.y);
});
return {
x: x,
y: y,
points: points
};
}; // Exit tooltip element.
var exitElement = function exitElement(el) {
el.transition().duration(50).style('opacity', 0).remove();
}; // Enter tooltip element.
var enterElement = function enterElement(el) {
el.transition().duration(50).style('opacity', 1);
}; // Enter tooltip components.
var enter = function enter(d, i) {
var markerX = $$.svgContainer.selectAll('.d2b-tooltip-marker-x').data($$.trackX ? [tooltip] : []);
var markerXEnter = markerX.enter().append('line').attr('class', 'd2b-tooltip-marker-x d2b-tooltip-marker');
var markerY = $$.svgContainer.selectAll('.d2b-tooltip-marker-y').data($$.trackY ? [tooltip] : []);
var markerYEnter = markerY.enter().append('line').attr('class', 'd2b-tooltip-marker-y d2b-tooltip-marker');
var tooltipEl = $$.htmlContainer.selectAll('.d2b-tooltip-axis').data([tooltip]);
var tooltipEnter = tooltipEl.enter().append('div').attr('class', 'd2b-tooltip d2b-tooltip-axis');
tooltipEnter.merge(tooltipEl).call(enterElement);
markerY.merge(markerYEnter).call(enterElement);
markerX.merge(markerXEnter).call(enterElement);
tooltipEnter.append('div').attr('class', 'd2b-tooltip-title');
tooltipEnter.append('div').attr('class', 'd2b-tooltip-content');
$$.dispatch.call('insert', $$.tooltip, d, i);
}; // Exit tooltip components.
var exit = function exit() {
$$.svgContainer.selectAll('.d2b-tooltip-marker-x').data([]).exit().call(exitElement);
$$.svgContainer.selectAll('.d2b-tooltip-marker-y').data([]).exit().call(exitElement);
$$.htmlContainer.selectAll('.d2b-tooltip-axis').data([]).exit().call(exitElement);
}; // Tracker mousemove event.
var mousemove = function mousemove(d, i) {
var base = $$.svgContainer.selectAll('.d2b-tooltip-base').data([d]);
base.enter().append('rect').attr('class', 'd2b-tooltip-base');
var baseBox = $$.svgContainer.node().getBoundingClientRect();
baseBox = {
x: baseBox.left,
y: baseBox.top
};
var pointInfo = findPointInfo(baseBox);
if (pointInfo.points.length) enter();else return exit();
$$.svgContainer.select('.d2b-tooltip-marker-x').call(positionMarker, pointInfo, 'x');
$$.svgContainer.select('.d2b-tooltip-marker-y').call(positionMarker, pointInfo, 'y');
$$.htmlContainer.select('.d2b-tooltip-axis').call(populateTooltip, pointInfo).call(positionTooltip, pointInfo, baseBox);
$$.dispatch.call('move', $$.tooltip, d, i);
}; // Tracker mouseout event.
var mouseout = function mouseout(d, i) {
exit();
$$.dispatch.call('remove', $$.tooltip, d, i);
}; // Event key builder.
var event = function event(listener) {
return "".concat(listener, ".d2b-tooltip-axis");
}; // update mouse event tracker
var updateTracker = function updateTracker(n, o) {
if (o) {
o.on(event('mouseout'), null).on(event('mousemove'), null);
}
if (n) {
n.on(event('mouseout'), mouseout).on(event('mousemove'), mousemove);
}
}; // setup tooltip model
base(tooltip, $$).addProp('htmlContainer', d3Selection.select('body')).addProp('svgContainer', null).addProp('tracker', d3Selection.select('body'), null, updateTracker).addProp('size', {
height: 0,
width: 0
}).addProp('trackX', true).addProp('trackY', false).addProp('threshold', Infinity).addMethod('clear', function (groupName, graphName) {
if (arguments.length === 0) groups = {};else if (arguments.length === 1) delete groups[groupName];else if (arguments.length >= 2) delete groups[groupName][graphName];
return tooltip;
}).addPropFunctor('title', null).addPropFunctor('x', function (d) {
return d.x;
}).addPropFunctor('y', function (d) {
return d.y;
}).addPropFunctor('color', null).addPropFunctor('row', null).addDispatcher(['move', 'insert', 'remove']); // construct an interface for each graph that is initialized
var groups = {};
tooltip.graph = function () {
var groupName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var graphName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var graphs = groups[groupName] = groups[groupName] || {};
var graph = graphs[graphName];
if (!graph) {
graph = graphs[graphName] = {
interface: {},
config: {}
};
var graphModel = base(graph.interface, graph.config);
graphModel.addProp('data', []).addMethod('clear', function () {
graph.config.data = [];
return graph.interface;
}).addMethod('addPoint', function (p) {
graph.config.data.push(p);
return graph.interface;
}).addPropFunctor('x', null).addPropFunctor('y', null).addPropFunctor('color', null).addPropFunctor('row', function () {
return undefined;
});
}
return graph.interface;
};
return tooltip;
}
// some datum and d3.stack configuration.
function stack () {
var $$ = {};
var stack = function stack(datum) {
var _this = this;
var original = datum; // for simplicity map datum to just array of values arrays
datum = datum.map($$.values); // format values to be in the form
// [
// {x_1: y_1, x_2: y_2, .. },
// {x_1: y_1, x_2: y_2, .. },
// ..
// ]
var xset = [];
var vals = datum.map(function (d) {
var nodes = {};
d.forEach(function (d, i) {
var x = $$.x.call(_this, d, i);
xset.push(x);
nodes[x] = $$.y.call(_this, d, i);
});
return nodes;
}); // find unique set of x values
xset = d3Collection.set(xset).values(); // value => index mapping of x values
var xmap = xset.reduce(function (o, v, i) {
o[v] = i;
return o;
}, {}); // graph keys (just use index)
var keys = d3Array.range(0, datum.length); // transpose values for d3.stack
var tvals = xset.map(function (col) {
return vals.map(function (row) {
return row[col] || 0;
});
}); // stack transposed values
$$.stack.keys(keys).value(function (d, k) {
return d[k] || 0;
});
var stacking = $$.stack(tvals); // reassociate the stacked values with the original datum
datum.forEach(function (d, i) {
d.forEach(function (val, ind) {
var x = $$.x.call(_this, val, ind);
var ys = stacking[i][xmap[x]];
$$.out.call(_this, val, ys[0], ys[1], x);
});
});
return original;
};
/* Inherit from base model */
base(stack, $$).addProp('stack', d3Shape.stack()).addPropFunctor('values', function (d) {
return d;
}).addPropFunctor('x', function (d) {
return d.x;
}).addPropFunctor('y', function (d) {
return d.y;
}).addProp('out', function (d, y0, y1) {
d.y0 = y0;
d.y1 = y1;
});
return stack;
}
function breadcrumbs () {
var $$ = {};
var breadcrumbs = function breadcrumbs(context) {
var selection = context.selection ? context.selection() : context;
var bcs = selection.selectAll('.d2b-breadcrumbs').data(function (d) {
return [d];
});
var bcsEnter = bcs.enter().append('div').attr('class', 'd2b-breadcrumbs');
bcs = bcs.merge(bcsEnter).classed('d2b-vertical', $$.vertical); // Set breadcrumb data from values accessor. And only show breadcrumbs that have html content.
var bc = bcs.selectAll('.d2b-breadcrumb').data(function (d) {
return $$.values(d).filter($$.html);
}, $$.key),
bcExit = bc.exit();
var bcEnter = bc.enter().append('div').attr('class', 'd2b-breadcrumb').style('opacity', 0);
bcEnter.append('div').attr('class', 'd2b-breadcrumb-icon');
bcEnter.append('div').attr('class', 'd2b-breadcrumb-content');
bc = bc.merge(bcEnter).order();
bc.select('.d2b-breadcrumb-content').html($$.html);
if (context !== selection) {
bc = bc.transition(context);
bcExit = bcExit.transition(context).style('opacity', 0);
}
bc.style('border-color', $$.color).style('opacity', 1).select('.d2b-breadcrumb-icon').style('background-color', $$.color);
bcExit.remove();
selection.dispatch('breadcrumbs-updated', {
bubbles: true
});
return breadcrumbs;
};
/* Inherit from base model */
base(breadcrumbs, $$).addPropFunctor('values', function (d) {
return d;
}).addPropFunctor('key', function (d, i) {
return i;
}).addPropFunctor('color', 'blue').addPropFunctor('html', function (d) {
return d.html;
}).addPropFunctor('vertical', true);
return breadcrumbs;
}
var color = d3Scales.scaleOrdinal(d3ScaleChromatic.schemeCategory10);
function legend () {
var $$ = {};
var symbol = d3Shape.symbol().size(80);
var legend = function legend(context) {
var selection = context.selection ? context.selection() : context;
var leg = selection.selectAll('.d2b-legend').data(function (d) {
return [d];
});
var legEnter = leg.enter().append('div').attr('class', 'd2b-legend');
leg = leg.merge(legEnter).classed('d2b-vertical', $$.vertical);
var legItem = leg.selectAll('.d2b-legend-item').data($$.values, $$.key),
legItemExit = legItem.exit();
var legItemEnter = legItem.enter().append('div').attr('class', 'd2b-legend-item').style('opacity', 0);
legItemEnter.append('div').attr('class', 'd2b-legend-icon');
legItemEnter.append('div').attr('class', 'd2b-legend-content');
legItem = legItem.merge(legItemEnter).order();
legItem.select('.d2b-legend-content').html($$.html);
if (context !== selection) {
legItem = legItem.transition(context);
legItemExit = legItemExit.transition(context).style('opacity', 0);
}
legItem.style('opacity', 1).each(function (d, i) {
// legend item customization
var item = d3Selection.select(this);
var color = $$.color(d, i),
empty = $$.empty(d, i); // legend icon customization
var icon = $$.icon(d, i),
iconDiv = item.select('.d2b-legend-icon'),
fa = [],
shape = [],
rect = iconDiv.node().getBoundingClientRect(),
size = {
width: rect.width,
height: rect.