Socket
Socket
Sign inDemoInstall

d3-funnel

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

d3-funnel - npm Package Compare versions

Comparing version 0.6.9 to 0.6.10

gulpfile.js

1121

dist/d3-funnel.js

@@ -1,3 +0,9 @@

(function(global, d3) {
'use strict';
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
(function (global, d3) {
/* global d3 */

@@ -7,651 +13,621 @@ /* jshint bitwise: false */

/**
* D3Funnel
*
* @param {string} selector A selector for the container element.
*
* @return {void}
*/
var D3Funnel = function(selector)
{
this.selector = selector;
var D3Funnel = (function () {
// Default configuration values
this.defaults = {
width: 350,
height: 400,
bottomWidth: 1 / 3,
bottomPinch: 0,
isCurved: false,
curveHeight: 20,
fillType: 'solid',
isInverted: false,
hoverEffects: false,
dynamicArea: false,
minHeight: false,
animation: false,
label: {
fontSize: '14px',
fill: '#fff'
}
};
};
/**
* @param {string} selector A selector for the container element.
*
* @return {void}
*/
/**
* Draw the chart inside the container with the data and configuration
* specified. This will remove any previous SVG elements in the container
* and draw a new funnel chart on top of it.
*
* @param {array} data A list of rows containing a category, a count,
* and optionally a color (in hex).
* @param {Object} options An optional configuration object to override
* defaults. See the docs.
*
* @return {void}
*/
D3Funnel.prototype.draw = function(data, options)
{
// Initialize chart options
this._initialize(data, options);
function D3Funnel(selector) {
_classCallCheck(this, D3Funnel);
// Remove any previous drawings
d3.select(this.selector).selectAll('svg').remove();
this.selector = selector;
// Add the SVG
this.svg = d3.select(this.selector)
.append('svg')
.attr('width', this.width)
.attr('height', this.height);
this.sectionPaths = this._makePaths();
// Define color gradients
if (this.fillType === 'gradient') {
this._defineColorGradients(this.svg);
// Default configuration values
this.defaults = {
width: 350,
height: 400,
bottomWidth: 1 / 3,
bottomPinch: 0,
isCurved: false,
curveHeight: 20,
fillType: 'solid',
isInverted: false,
hoverEffects: false,
dynamicArea: false,
minHeight: false,
animation: false,
label: {
fontSize: '14px',
fill: '#fff'
}
};
}
// Add top oval if curved
if (this.isCurved) {
this._drawTopOval(this.svg, this.sectionPaths);
}
_createClass(D3Funnel, [{
key: 'draw',
// Add each block section
this._drawSection(0);
};
/**
* Draw the chart inside the container with the data and configuration
* specified. This will remove any previous SVG elements in the container
* and draw a new funnel chart on top of it.
*
* @param {Array} data A list of rows containing a category, a count,
* and optionally a color (in hex).
* @param {Object} options An optional configuration object to override
* defaults. See the docs.
*
* @return {void}
*/
value: function draw(data, options) {
// Initialize chart options
this._initialize(data, options);
/**
* Initialize and calculate important variables for drawing the chart.
*
* @param {array} data
* @param {Object} options
*
* @return {void}
*/
D3Funnel.prototype._initialize = function(data, options)
{
if (!this._isArray(data) || data.length === 0 ||
!this._isArray(data[0]) || data[0].length < 2) {
throw {
name: 'D3 Funnel Data Error',
message: 'Funnel data is not valid.'
};
}
// Remove any previous drawings
d3.select(this.selector).selectAll('svg').remove();
// Initialize options if not set
options = typeof options !== 'undefined' ? options : {};
// Add the SVG
this.svg = d3.select(this.selector).append('svg').attr('width', this.width).attr('height', this.height);
this.data = data;
this.sectionPaths = this._makePaths();
// Counter
var i = 0;
// Define color gradients
if (this.fillType === 'gradient') {
this._defineColorGradients(this.svg);
}
// Prepare the configuration settings based on the defaults
// Set the default width and height based on the container
var settings = this._extend({}, this.defaults);
settings.width = parseInt(d3.select(this.selector).style('width'), 10);
settings.height = parseInt(d3.select(this.selector).style('height'), 10);
// Add top oval if curved
if (this.isCurved) {
this._drawTopOval(this.svg, this.sectionPaths);
}
// Overwrite default settings with user options
var keys = Object.keys(options);
for (i = 0; i < keys.length; i++) {
if (keys[i] !== 'label') {
settings[keys[i]] = options[keys[i]];
// Add each block section
this._drawSection(0);
}
}
}, {
key: '_initialize',
// Label settings
if ('label' in options) {
var validLabelOptions = /fontSize|fill/;
var labelOption;
for (labelOption in options.label) {
if (labelOption.match(validLabelOptions)) {
settings.label[labelOption] = options.label[labelOption];
/**
* Initialize and calculate important variables for drawing the chart.
*
* @param {Array} data
* @param {Object} options
*
* @return {void}
*/
value: function _initialize(data, options) {
if (!isArray(data) || data.length === 0 || !isArray(data[0]) || data[0].length < 2) {
throw {
name: 'D3 Funnel Data Error',
message: 'Funnel data is not valid.'
};
}
}
}
this.label = settings.label;
// In the case that the width or height is not valid, set
// the width/height as its default hard-coded value
if (settings.width <= 0) {
settings.width = this.defaults.width;
}
if (settings.height <= 0) {
settings.height = this.defaults.height;
}
// Initialize options if not set
options = typeof options !== 'undefined' ? options : {};
// Initialize the colors for each block section
var colorScale = d3.scale.category10();
for (i = 0; i < this.data.length; i++) {
var hexExpression = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
this.data = data;
// If a color is not set for the record, add one
if (!('2' in this.data[i]) || !hexExpression.test(this.data[i][2])) {
this.data[i][2] = colorScale(i);
}
}
// Counter
var i;
// Initialize funnel chart settings
this.width = settings.width;
this.height = settings.height;
this.bottomWidth = settings.width * settings.bottomWidth;
this.bottomPinch = settings.bottomPinch;
this.isCurved = settings.isCurved;
this.curveHeight = settings.curveHeight;
this.fillType = settings.fillType;
this.isInverted = settings.isInverted;
this.hoverEffects = settings.hoverEffects;
this.dynamicArea = settings.dynamicArea;
this.minHeight = settings.minHeight;
this.animation = settings.animation;
// Prepare the configuration settings based on the defaults
// Set the default width and height based on the container
var settings = extend({}, this.defaults);
settings.width = parseInt(d3.select(this.selector).style('width'), 10);
settings.height = parseInt(d3.select(this.selector).style('height'), 10);
// Calculate the bottom left x position
this.bottomLeftX = (this.width - this.bottomWidth) / 2;
// Overwrite default settings with user options
var keys = Object.keys(options);
for (i = 0; i < keys.length; i++) {
if (keys[i] !== 'label') {
settings[keys[i]] = options[keys[i]];
}
}
// Change in x direction
// Will be sharper if there is a pinch
this.dx = this.bottomPinch > 0 ?
this.bottomLeftX / (data.length - this.bottomPinch) :
this.bottomLeftX / data.length;
// Change in y direction
// Curved chart needs reserved pixels to account for curvature
this.dy = this.isCurved ?
(this.height - this.curveHeight) / data.length :
this.height / data.length;
// Label settings
if (options.hasOwnProperty('label')) {
var validLabelOptions = /fontSize|fill/;
var labelOption;
for (labelOption in options.label) {
if (labelOption.match(validLabelOptions)) {
settings.label[labelOption] = options.label[labelOption];
}
}
}
this.label = settings.label;
// Support for events
this.onItemClick = settings.onItemClick;
};
// In the case that the width or height is not valid, set
// the width/height as its default hard-coded value
if (settings.width <= 0) {
settings.width = this.defaults.width;
}
if (settings.height <= 0) {
settings.height = this.defaults.height;
}
/**
* Create the paths to be used to define the discrete funnel sections and
* returns the results in an array.
*
* @return {array}
*/
D3Funnel.prototype._makePaths = function()
{
var paths = [];
// Initialize the colors for each block section
var colorScale = d3.scale.category10();
for (i = 0; i < this.data.length; i++) {
var hexExpression = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
// Initialize velocity
var dx = this.dx;
var dy = this.dy;
// If a color is not set for the record, add one
if (!('2' in this.data[i]) || !hexExpression.test(this.data[i][2])) {
this.data[i][2] = colorScale(i);
}
}
// Initialize starting positions
var prevLeftX = 0;
var prevRightX = this.width;
var prevHeight = 0;
// Initialize funnel chart settings
this.width = settings.width;
this.height = settings.height;
this.bottomWidth = settings.width * settings.bottomWidth;
this.bottomPinch = settings.bottomPinch;
this.isCurved = settings.isCurved;
this.curveHeight = settings.curveHeight;
this.fillType = settings.fillType;
this.isInverted = settings.isInverted;
this.hoverEffects = settings.hoverEffects;
this.dynamicArea = settings.dynamicArea;
this.minHeight = settings.minHeight;
this.animation = settings.animation;
// Start from the bottom for inverted
if (this.isInverted) {
prevLeftX = this.bottomLeftX;
prevRightX = this.width - this.bottomLeftX;
}
// Calculate the bottom left x position
this.bottomLeftX = (this.width - this.bottomWidth) / 2;
// Initialize next positions
var nextLeftX = 0;
var nextRightX = 0;
var nextHeight = 0;
// Change in x direction
// Will be sharper if there is a pinch
this.dx = this.bottomPinch > 0 ? this.bottomLeftX / (data.length - this.bottomPinch) : this.bottomLeftX / data.length;
// Change in y direction
// Curved chart needs reserved pixels to account for curvature
this.dy = this.isCurved ? (this.height - this.curveHeight) / data.length : this.height / data.length;
var middle = this.width / 2;
// Support for events
this.onItemClick = settings.onItemClick;
}
}, {
key: '_makePaths',
// Move down if there is an initial curve
if (this.isCurved) {
prevHeight = 10;
}
/**
* Create the paths to be used to define the discrete funnel sections and
* returns the results in an array.
*
* @return {Array}
*/
value: function _makePaths() {
var paths = [];
var topBase = this.width;
var bottomBase = 0;
// Initialize velocity
var dx = this.dx;
var dy = this.dy;
var totalArea = this.height * (this.width + this.bottomWidth) / 2;
var slope = 2 * this.height / (this.width - this.bottomWidth);
// Initialize starting positions
var prevLeftX = 0;
var prevRightX = this.width;
var prevHeight = 0;
// This is greedy in that the section will have a guranteed height and
// the remaining is shared among the ratio, instead of being shared
// according to the remaining minus the guranteed
if (this.minHeight !== false) {
var height = (this.height - this.minHeight * this.data.length);
totalArea = height * (this.width + this.bottomWidth) / 2;
}
// Start from the bottom for inverted
if (this.isInverted) {
prevLeftX = this.bottomLeftX;
prevRightX = this.width - this.bottomLeftX;
}
var totalCount = 0;
var count = 0;
// Initialize next positions
var nextLeftX = 0;
var nextRightX = 0;
var nextHeight = 0;
// Harvest total count
for (var i = 0; i < this.data.length; i++) {
totalCount += this._isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
}
var middle = this.width / 2;
// Create the path definition for each funnel section
// Remember to loop back to the beginning point for a closed path
for (i = 0; i < this.data.length; i++) {
count = this._isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
// Move down if there is an initial curve
if (this.isCurved) {
prevHeight = 10;
}
// Calculate dynamic shapes based on area
if (this.dynamicArea) {
var ratio = count / totalCount;
var area = ratio * totalArea;
var topBase = this.width;
var bottomBase = 0;
var totalArea = this.height * (this.width + this.bottomWidth) / 2;
var slope = 2 * this.height / (this.width - this.bottomWidth);
// This is greedy in that the section will have a guranteed height and
// the remaining is shared among the ratio, instead of being shared
// according to the remaining minus the guranteed
if (this.minHeight !== false) {
area += this.minHeight * (this.width + this.bottomWidth) / 2;
var height = this.height - this.minHeight * this.data.length;
totalArea = height * (this.width + this.bottomWidth) / 2;
}
bottomBase = Math.sqrt((slope * topBase * topBase - (4 * area)) / slope);
dx = (topBase / 2) - (bottomBase / 2);
dy = (area * 2) / (topBase + bottomBase);
var totalCount = 0;
var count = 0;
if (this.isCurved) {
dy = dy - (this.curveHeight / this.data.length);
// Harvest total count
for (var i = 0; i < this.data.length; i++) {
totalCount += isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
}
topBase = bottomBase;
}
// Create the path definition for each funnel section
// Remember to loop back to the beginning point for a closed path
for (i = 0; i < this.data.length; i++) {
count = isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
// Stop velocity for pinched sections
if (this.bottomPinch > 0) {
// Check if we've reached the bottom of the pinch
// If so, stop changing on x
if (!this.isInverted) {
if (i >= this.data.length - this.bottomPinch) {
dx = 0;
// Calculate dynamic shapes based on area
if (this.dynamicArea) {
var ratio = count / totalCount;
var area = ratio * totalArea;
if (this.minHeight !== false) {
area += this.minHeight * (this.width + this.bottomWidth) / 2;
}
bottomBase = Math.sqrt((slope * topBase * topBase - 4 * area) / slope);
dx = topBase / 2 - bottomBase / 2;
dy = area * 2 / (topBase + bottomBase);
if (this.isCurved) {
dy = dy - this.curveHeight / this.data.length;
}
topBase = bottomBase;
}
// Pinch at the first sections relating to the bottom pinch
// Revert back to normal velocity after pinch
} else {
// Revert velocity back to the intial if we are using
// static area's (prevents zero velocity if isInverted
// and bottomPinch are non trivial and dynamicArea is false)
if (!this.dynamicArea) {
dx = this.dx;
// Stop velocity for pinched sections
if (this.bottomPinch > 0) {
// Check if we've reached the bottom of the pinch
// If so, stop changing on x
if (!this.isInverted) {
if (i >= this.data.length - this.bottomPinch) {
dx = 0;
}
// Pinch at the first sections relating to the bottom pinch
// Revert back to normal velocity after pinch
} else {
// Revert velocity back to the intial if we are using
// static area's (prevents zero velocity if isInverted
// and bottomPinch are non trivial and dynamicArea is false)
if (!this.dynamicArea) {
dx = this.dx;
}
dx = i < this.bottomPinch ? 0 : dx;
}
}
dx = i < this.bottomPinch ? 0 : dx;
}
}
// Calculate the position of next section
nextLeftX = prevLeftX + dx;
nextRightX = prevRightX - dx;
nextHeight = prevHeight + dy;
// Calculate the position of next section
nextLeftX = prevLeftX + dx;
nextRightX = prevRightX - dx;
nextHeight = prevHeight + dy;
// Expand outward if inverted
if (this.isInverted) {
nextLeftX = prevLeftX - dx;
nextRightX = prevRightX + dx;
}
// Expand outward if inverted
if (this.isInverted) {
nextLeftX = prevLeftX - dx;
nextRightX = prevRightX + dx;
}
// Plot curved lines
if (this.isCurved) {
paths.push([
// Top Bezier curve
[prevLeftX, prevHeight, 'M'], [middle, prevHeight + (this.curveHeight - 10), 'Q'], [prevRightX, prevHeight, ''],
// Right line
[nextRightX, nextHeight, 'L'],
// Bottom Bezier curve
[nextRightX, nextHeight, 'M'], [middle, nextHeight + this.curveHeight, 'Q'], [nextLeftX, nextHeight, ''],
// Left line
[prevLeftX, prevHeight, 'L']]);
// Plot straight lines
} else {
paths.push([
// Start position
[prevLeftX, prevHeight, 'M'],
// Move to right
[prevRightX, prevHeight, 'L'],
// Move down
[nextRightX, nextHeight, 'L'],
// Move to left
[nextLeftX, nextHeight, 'L'],
// Wrap back to top
[prevLeftX, prevHeight, 'L']]);
}
// Plot curved lines
if (this.isCurved) {
paths.push([
// Top Bezier curve
[prevLeftX, prevHeight, 'M'],
[middle, prevHeight + (this.curveHeight - 10), 'Q'],
[prevRightX, prevHeight, ''],
// Right line
[nextRightX, nextHeight, 'L'],
// Bottom Bezier curve
[nextRightX, nextHeight, 'M'],
[middle, nextHeight + this.curveHeight, 'Q'],
[nextLeftX, nextHeight, ''],
// Left line
[prevLeftX, prevHeight, 'L']
]);
// Plot straight lines
} else {
paths.push([
// Start position
[prevLeftX, prevHeight, 'M'],
// Move to right
[prevRightX, prevHeight, 'L'],
// Move down
[nextRightX, nextHeight, 'L'],
// Move to left
[nextLeftX, nextHeight, 'L'],
// Wrap back to top
[prevLeftX, prevHeight, 'L']
]);
// Set the next section's previous position
prevLeftX = nextLeftX;
prevRightX = nextRightX;
prevHeight = nextHeight;
}
return paths;
}
}, {
key: '_defineColorGradients',
// Set the next section's previous position
prevLeftX = nextLeftX;
prevRightX = nextRightX;
prevHeight = nextHeight;
}
/**
* Define the linear color gradients.
*
* @param {Object} svg
*
* @return {void}
*/
value: function _defineColorGradients(svg) {
var defs = svg.append('defs');
return paths;
};
// Create a gradient for each section
for (var i = 0; i < this.data.length; i++) {
var color = this.data[i][2];
var shade = shadeColor(color, -0.25);
/**
* Define the linear color gradients.
*
* @param {Object} svg
*
* @return {void}
*/
D3Funnel.prototype._defineColorGradients = function(svg)
{
var defs = svg.append('defs');
// Create linear gradient
var gradient = defs.append('linearGradient').attr({
id: 'gradient-' + i
});
// Create a gradient for each section
for (var i = 0; i < this.data.length; i++) {
var color = this.data[i][2];
var shade = shadeColor(color, -0.25);
// Define the gradient stops
var stops = [[0, shade], [40, color], [60, color], [100, shade]];
// Create linear gradient
var gradient = defs.append('linearGradient')
.attr({
id: 'gradient-' + i
});
// Define the gradient stops
var stops = [
[0, shade],
[40, color],
[60, color],
[100, shade]
];
// Add the gradient stops
for (var j = 0; j < stops.length; j++) {
var stop = stops[j];
gradient.append('stop').attr({
offset: stop[0] + '%',
style: 'stop-color:' + stop[1]
});
// Add the gradient stops
for (var j = 0; j < stops.length; j++) {
var stop = stops[j];
gradient.append('stop').attr({
offset: stop[0] + '%',
style: 'stop-color:' + stop[1]
});
}
}
}
}
};
}, {
key: '_drawTopOval',
/**
* Draw the top oval of a curved funnel.
*
* @param {Object} svg
* @param {array} sectionPaths
*
* @return {void}
*/
D3Funnel.prototype._drawTopOval = function(svg, sectionPaths)
{
var leftX = 0;
var rightX = this.width;
var centerX = this.width / 2;
/**
* Draw the top oval of a curved funnel.
*
* @param {Object} svg
* @param {Array} sectionPaths
*
* @return {void}
*/
value: function _drawTopOval(svg, sectionPaths) {
var leftX = 0;
var rightX = this.width;
var centerX = this.width / 2;
if (this.isInverted) {
leftX = this.bottomLeftX;
rightX = this.width - this.bottomLeftX;
}
if (this.isInverted) {
leftX = this.bottomLeftX;
rightX = this.width - this.bottomLeftX;
}
// Create path form top-most section
var paths = sectionPaths[0];
var path = 'M' + leftX + ',' + paths[0][1] +
' Q' + centerX + ',' + (paths[1][1] + this.curveHeight - 10) +
' ' + rightX + ',' + paths[2][1] +
' M' + rightX + ',10' +
' Q' + centerX + ',0' +
' ' + leftX + ',10';
// Create path form top-most section
var paths = sectionPaths[0];
var path = 'M' + leftX + ',' + paths[0][1] + ' Q' + centerX + ',' + (paths[1][1] + this.curveHeight - 10) + ' ' + rightX + ',' + paths[2][1] + ' M' + rightX + ',10' + ' Q' + centerX + ',0' + ' ' + leftX + ',10';
// Draw top oval
svg.append('path')
.attr('fill', shadeColor(this.data[0][2], -0.4))
.attr('d', path);
};
// Draw top oval
svg.append('path').attr('fill', shadeColor(this.data[0][2], -0.4)).attr('d', path);
}
}, {
key: '_drawSection',
/**
* Draw the next section in the iteration.
*
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._drawSection = function(index)
{
if (index === this.data.length) {
return;
}
/**
* Draw the next section in the iteration.
*
* @param {int} index
*
* @return {void}
*/
value: function _drawSection(index) {
if (index === this.data.length) {
return;
}
// Create a group just for this block
var group = this.svg.append('g');
// Create a group just for this block
var group = this.svg.append('g');
// Fetch path element
var path = this._getSectionPath(group, index);
path.data(this._getSectionData(index));
// Fetch path element
var path = this._getSectionPath(group, index);
path.data(this._getSectionData(index));
// Add animation components
if (this.animation !== false) {
var self = this;
path.transition()
.duration(this.animation)
.ease('linear')
.attr('fill', this._getColor(index))
.attr('d', this._getPathDefinition(index))
.each('end', function() {
self._drawSection(index + 1);
});
} else {
path.attr('fill', this._getColor(index))
.attr('d', this._getPathDefinition(index));
this._drawSection(index + 1);
}
// Add animation components
if (this.animation !== false) {
var self = this;
path.transition().duration(this.animation).ease('linear').attr('fill', this._getColor(index)).attr('d', this._getPathDefinition(index)).each('end', function () {
self._drawSection(index + 1);
});
} else {
path.attr('fill', this._getColor(index)).attr('d', this._getPathDefinition(index));
this._drawSection(index + 1);
}
// Add the hover events
if (this.hoverEffects) {
path.on('mouseover', this._onMouseOver)
.on('mouseout', this._onMouseOut);
}
// Add the hover events
if (this.hoverEffects) {
path.on('mouseover', this._onMouseOver).on('mouseout', this._onMouseOut);
}
// ItemClick event
if (this.onItemClick) {
path.on('click', this.onItemClick);
}
// ItemClick event
if (this.onItemClick) {
path.on('click', this.onItemClick);
}
this._addSectionLabel(group, index);
};
this._addSectionLabel(group, index);
}
}, {
key: '_getSectionPath',
/**
* @param {Object} group
* @param {int} index
*
* @return {Object}
*/
D3Funnel.prototype._getSectionPath = function(group, index)
{
var path = group.append('path');
/**
* @param {Object} group
* @param {int} index
*
* @return {Object}
*/
value: function _getSectionPath(group, index) {
var path = group.append('path');
if (this.animation !== false) {
this._addBeforeTransition(path, index);
}
if (this.animation !== false) {
this._addBeforeTransition(path, index);
}
return path;
};
return path;
}
}, {
key: '_addBeforeTransition',
/**
* Set the attributes of a path element before its animation.
*
* @param {Object} path
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._addBeforeTransition = function(path, index)
{
var paths = this.sectionPaths[index];
/**
* Set the attributes of a path element before its animation.
*
* @param {Object} path
* @param {int} index
*
* @return {void}
*/
value: function _addBeforeTransition(path, index) {
var paths = this.sectionPaths[index];
var beforePath = '';
var beforeFill = '';
var beforePath = '';
var beforeFill = '';
// Construct the top of the trapezoid and leave the other elements
// hovering around to expand downward on animation
if (!this.isCurved) {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] +
' L' + paths[1][0] + ',' + paths[1][1] +
' L' + paths[1][0] + ',' + paths[1][1] +
' L' + paths[0][0] + ',' + paths[0][1];
} else {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] +
' Q' + paths[1][0] + ',' + paths[1][1] +
' ' + paths[2][0] + ',' + paths[2][1] +
' L' + paths[2][0] + ',' + paths[2][1] +
' M' + paths[2][0] + ',' + paths[2][1] +
' Q' + paths[1][0] + ',' + paths[1][1] +
' ' + paths[0][0] + ',' + paths[0][1];
}
// Construct the top of the trapezoid and leave the other elements
// hovering around to expand downward on animation
if (!this.isCurved) {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] + ' L' + paths[1][0] + ',' + paths[1][1] + ' L' + paths[1][0] + ',' + paths[1][1] + ' L' + paths[0][0] + ',' + paths[0][1];
} else {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] + ' Q' + paths[1][0] + ',' + paths[1][1] + ' ' + paths[2][0] + ',' + paths[2][1] + ' L' + paths[2][0] + ',' + paths[2][1] + ' M' + paths[2][0] + ',' + paths[2][1] + ' Q' + paths[1][0] + ',' + paths[1][1] + ' ' + paths[0][0] + ',' + paths[0][1];
}
// Use previous fill color, if available
if (this.fillType === 'solid') {
beforeFill = index > 0 ? this._getColor(index - 1) : this._getColor(index);
// Use current background if gradient (gradients do not transition)
} else {
beforeFill = this._getColor(index);
}
// Use previous fill color, if available
if (this.fillType === 'solid') {
beforeFill = index > 0 ? this._getColor(index - 1) : this._getColor(index);
// Use current background if gradient (gradients do not transition)
} else {
beforeFill = this._getColor(index);
}
path.attr('d', beforePath)
.attr('fill', beforeFill);
};
path.attr('d', beforePath).attr('fill', beforeFill);
}
}, {
key: '_getSectionData',
/**
* @param {int} index
*
* @return {array}
*/
D3Funnel.prototype._getSectionData = function(index)
{
return [{
index: index,
label: this.data[index][0],
value: this._isArray(this.data[index][1]) ? this.data[index][1][0] : this.data[index][1], formattedValue: this._isArray(this.data[index][1]) ? this.data[index][1][1] : this.data[index][1].toLocaleString(),
baseColor: this.data[index][2],
fill: this._getColor(index)
}];
};
/**
* @param {int} index
*
* @return {Array}
*/
value: function _getSectionData(index) {
return [{
index: index,
label: this.data[index][0],
value: isArray(this.data[index][1]) ? this.data[index][1][0] : this.data[index][1], formattedValue: isArray(this.data[index][1]) ? this.data[index][1][1] : this.data[index][1].toLocaleString(),
baseColor: this.data[index][2],
fill: this._getColor(index)
}];
}
}, {
key: '_getColor',
/**
* Return the color for the given index.
*
* @param {int} index
*
* @return {string}
*/
value: function _getColor(index) {
if (this.fillType === 'solid') {
return this.data[index][2];
} else {
return 'url(#gradient-' + index + ')';
}
}
}, {
key: '_getPathDefinition',
/**
* Return the color for the given index.
*
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._getColor = function(index)
{
if (this.fillType === 'solid') {
return this.data[index][2];
} else {
return 'url(#gradient-' + index + ')';
}
};
/**
* @param {int} index
*
* @return {string}
*/
value: function _getPathDefinition(index) {
var pathStr = '';
var point = [];
var paths = this.sectionPaths[index];
/**
* @param {int} index
*
* @return {string}
*/
D3Funnel.prototype._getPathDefinition = function(index)
{
var pathStr = '';
var point = [];
var paths = this.sectionPaths[index];
for (var j = 0; j < paths.length; j++) {
point = paths[j];
pathStr += point[2] + point[0] + ',' + point[1] + ' ';
}
for (var j = 0; j < paths.length; j++) {
point = paths[j];
pathStr += point[2] + point[0] + ',' + point[1] + ' ';
}
return pathStr;
}
}, {
key: '_onMouseOver',
return pathStr;
};
/**
* @param {Object} data
*
* @return {void}
*/
value: function _onMouseOver(data) {
d3.select(this).attr('fill', shadeColor(data.baseColor, -0.2));
}
}, {
key: '_onMouseOut',
/**
* @param {Object} data
*
* @return {void}
*/
D3Funnel.prototype._onMouseOver = function(data)
{
d3.select(this).attr('fill', shadeColor(data.baseColor, -0.2));
};
/**
* @param {Object} data
*
* @return {void}
*/
value: function _onMouseOut(data) {
d3.select(this).attr('fill', data.fill);
}
}, {
key: '_addSectionLabel',
/**
* @param {Object} data
*
* @return {void}
*/
D3Funnel.prototype._onMouseOut = function(data)
{
d3.select(this).attr('fill', data.fill);
};
/**
* @param {Object} group
* @param {int} index
*
* @return {void}
*/
value: function _addSectionLabel(group, index) {
var i = index;
var paths = this.sectionPaths[index];
var sectionData = this._getSectionData(index)[0];
var textStr = sectionData.label + ': ' + sectionData.formattedValue;
var textFill = this.data[i][3] || this.label.fill;
/**
* @param {Object} group
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._addSectionLabel = function(group, index)
{
var i = index;
var paths = this.sectionPaths[index];
var sectionData = this._getSectionData(index)[0];
var textStr = sectionData.label + ': ' + sectionData.formattedValue;
var textFill = this.data[i][3] || this.label.fill;
var textX = this.width / 2; // Center the text
var textY = !this.isCurved ? // Average height of bases
(paths[1][1] + paths[2][1]) / 2 : (paths[2][1] + paths[3][1]) / 2 + this.curveHeight / this.data.length;
var textX = this.width / 2; // Center the text
var textY = !this.isCurved ? // Average height of bases
(paths[1][1] + paths[2][1]) / 2 :
(paths[2][1] + paths[3][1]) / 2 + (this.curveHeight / this.data.length);
group.append('text').text(textStr).attr({
'x': textX,
'y': textY,
'text-anchor': 'middle',
'dominant-baseline': 'middle',
'fill': textFill,
'pointer-events': 'none'
}).style('font-size', this.label.fontSize);
}
}]);
group.append('text')
.text(textStr)
.attr({
'x': textX,
'y': textY,
'text-anchor': 'middle',
'dominant-baseline': 'middle',
'fill': textFill,
'pointer-events': 'none'
})
.style('font-size', this.label.fontSize);
};
return D3Funnel;
})();
/**
* Check if the supplied value is an array.
*
* @param {mixed} value
*
* @return {bool}
*/
D3Funnel.prototype._isArray = function(value)
{
* Check if the supplied value is an array.
*
* @param {*} value
*
* @return {bool}
*/
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
};
}
/**
* Extends an object with the members of another.
*
* @param {Object} a The object to be extended.
* @param {Object} b The object to clone from.
*
* @return {Object}
*/
D3Funnel.prototype._extend = function(a, b)
{
* Extends an object with the members of another.
*
* @param {Object} a The object to be extended.
* @param {Object} b The object to clone from.
*
* @return {Object}
*/
function extend(a, b) {
var prop;

@@ -664,23 +640,21 @@ for (prop in b) {

return a;
};
}
/**
* Shade a color to the given percentage.
*
* @param {string} color A hex color.
* @param {float} shade The shade adjustment. Can be positive or negative.
*
* @return {void}
*/
function shadeColor(color, shade)
{
* Shade a color to the given percentage.
*
* @param {string} color A hex color.
* @param {number} shade The shade adjustment. Can be positive or negative.
*
* @return {string}
*/
function shadeColor(color, shade) {
var f = parseInt(color.slice(1), 16);
var t = shade < 0 ? 0 : 255;
var p = shade < 0 ? shade * -1 : shade;
var R = f >> 16, G = f >> 8 & 0x00FF;
var B = f & 0x0000FF;
var R = f >> 16,
G = f >> 8 & 255;
var B = f & 255;
var converted = (0x1000000 + (Math.round((t - R) * p) + R) *
0x10000 + (Math.round((t - G) * p) + G) *
0x100 + (Math.round((t - B) * p) + B));
var converted = 16777216 + (Math.round((t - R) * p) + R) * 65536 + (Math.round((t - G) * p) + G) * 256 + (Math.round((t - B) * p) + B);

@@ -691,3 +665,2 @@ return '#' + converted.toString(16).slice(1);

global.D3Funnel = D3Funnel;
})(window, d3);
})(window, d3);

@@ -1,2 +0,2 @@

/*! d3-funnel - v0.6.9 | 2015-06-03 */
!function(a,b){"use strict";function c(a,b){var c=parseInt(a.slice(1),16),d=0>b?0:255,e=0>b?-1*b:b,f=c>>16,g=c>>8&255,h=255&c,i=16777216+65536*(Math.round((d-f)*e)+f)+256*(Math.round((d-g)*e)+g)+(Math.round((d-h)*e)+h);return"#"+i.toString(16).slice(1)}var d=function(a){this.selector=a,this.defaults={width:350,height:400,bottomWidth:1/3,bottomPinch:0,isCurved:!1,curveHeight:20,fillType:"solid",isInverted:!1,hoverEffects:!1,dynamicArea:!1,minHeight:!1,animation:!1,label:{fontSize:"14px",fill:"#fff"}}};d.prototype.draw=function(a,c){this._initialize(a,c),b.select(this.selector).selectAll("svg").remove(),this.svg=b.select(this.selector).append("svg").attr("width",this.width).attr("height",this.height),this.sectionPaths=this._makePaths(),"gradient"===this.fillType&&this._defineColorGradients(this.svg),this.isCurved&&this._drawTopOval(this.svg,this.sectionPaths),this._drawSection(0)},d.prototype._initialize=function(a,c){if(!this._isArray(a)||0===a.length||!this._isArray(a[0])||a[0].length<2)throw{name:"D3 Funnel Data Error",message:"Funnel data is not valid."};c="undefined"!=typeof c?c:{},this.data=a;var d=0,e=this._extend({},this.defaults);e.width=parseInt(b.select(this.selector).style("width"),10),e.height=parseInt(b.select(this.selector).style("height"),10);var f=Object.keys(c);for(d=0;d<f.length;d++)"label"!==f[d]&&(e[f[d]]=c[f[d]]);if("label"in c){var g,h=/fontSize|fill/;for(g in c.label)g.match(h)&&(e.label[g]=c.label[g])}this.label=e.label,e.width<=0&&(e.width=this.defaults.width),e.height<=0&&(e.height=this.defaults.height);var i=b.scale.category10();for(d=0;d<this.data.length;d++){var j=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i;"2"in this.data[d]&&j.test(this.data[d][2])||(this.data[d][2]=i(d))}this.width=e.width,this.height=e.height,this.bottomWidth=e.width*e.bottomWidth,this.bottomPinch=e.bottomPinch,this.isCurved=e.isCurved,this.curveHeight=e.curveHeight,this.fillType=e.fillType,this.isInverted=e.isInverted,this.hoverEffects=e.hoverEffects,this.dynamicArea=e.dynamicArea,this.minHeight=e.minHeight,this.animation=e.animation,this.bottomLeftX=(this.width-this.bottomWidth)/2,this.dx=this.bottomPinch>0?this.bottomLeftX/(a.length-this.bottomPinch):this.bottomLeftX/a.length,this.dy=this.isCurved?(this.height-this.curveHeight)/a.length:this.height/a.length,this.onItemClick=e.onItemClick},d.prototype._makePaths=function(){var a=[],b=this.dx,c=this.dy,d=0,e=this.width,f=0;this.isInverted&&(d=this.bottomLeftX,e=this.width-this.bottomLeftX);var g=0,h=0,i=0,j=this.width/2;this.isCurved&&(f=10);var k=this.width,l=0,m=this.height*(this.width+this.bottomWidth)/2,n=2*this.height/(this.width-this.bottomWidth);if(this.minHeight!==!1){var o=this.height-this.minHeight*this.data.length;m=o*(this.width+this.bottomWidth)/2}for(var p=0,q=0,r=0;r<this.data.length;r++)p+=this._isArray(this.data[r][1])?this.data[r][1][0]:this.data[r][1];for(r=0;r<this.data.length;r++){if(q=this._isArray(this.data[r][1])?this.data[r][1][0]:this.data[r][1],this.dynamicArea){var s=q/p,t=s*m;this.minHeight!==!1&&(t+=this.minHeight*(this.width+this.bottomWidth)/2),l=Math.sqrt((n*k*k-4*t)/n),b=k/2-l/2,c=2*t/(k+l),this.isCurved&&(c-=this.curveHeight/this.data.length),k=l}this.bottomPinch>0&&(this.isInverted?(this.dynamicArea||(b=this.dx),b=r<this.bottomPinch?0:b):r>=this.data.length-this.bottomPinch&&(b=0)),g=d+b,h=e-b,i=f+c,this.isInverted&&(g=d-b,h=e+b),a.push(this.isCurved?[[d,f,"M"],[j,f+(this.curveHeight-10),"Q"],[e,f,""],[h,i,"L"],[h,i,"M"],[j,i+this.curveHeight,"Q"],[g,i,""],[d,f,"L"]]:[[d,f,"M"],[e,f,"L"],[h,i,"L"],[g,i,"L"],[d,f,"L"]]),d=g,e=h,f=i}return a},d.prototype._defineColorGradients=function(a){for(var b=a.append("defs"),d=0;d<this.data.length;d++)for(var e=this.data[d][2],f=c(e,-.25),g=b.append("linearGradient").attr({id:"gradient-"+d}),h=[[0,f],[40,e],[60,e],[100,f]],i=0;i<h.length;i++){var j=h[i];g.append("stop").attr({offset:j[0]+"%",style:"stop-color:"+j[1]})}},d.prototype._drawTopOval=function(a,b){var d=0,e=this.width,f=this.width/2;this.isInverted&&(d=this.bottomLeftX,e=this.width-this.bottomLeftX);var g=b[0],h="M"+d+","+g[0][1]+" Q"+f+","+(g[1][1]+this.curveHeight-10)+" "+e+","+g[2][1]+" M"+e+",10 Q"+f+",0 "+d+",10";a.append("path").attr("fill",c(this.data[0][2],-.4)).attr("d",h)},d.prototype._drawSection=function(a){if(a!==this.data.length){var b=this.svg.append("g"),c=this._getSectionPath(b,a);if(c.data(this._getSectionData(a)),this.animation!==!1){var d=this;c.transition().duration(this.animation).ease("linear").attr("fill",this._getColor(a)).attr("d",this._getPathDefinition(a)).each("end",function(){d._drawSection(a+1)})}else c.attr("fill",this._getColor(a)).attr("d",this._getPathDefinition(a)),this._drawSection(a+1);this.hoverEffects&&c.on("mouseover",this._onMouseOver).on("mouseout",this._onMouseOut),this.onItemClick&&c.on("click",this.onItemClick),this._addSectionLabel(b,a)}},d.prototype._getSectionPath=function(a,b){var c=a.append("path");return this.animation!==!1&&this._addBeforeTransition(c,b),c},d.prototype._addBeforeTransition=function(a,b){var c=this.sectionPaths[b],d="",e="";d=this.isCurved?"M"+c[0][0]+","+c[0][1]+" Q"+c[1][0]+","+c[1][1]+" "+c[2][0]+","+c[2][1]+" L"+c[2][0]+","+c[2][1]+" M"+c[2][0]+","+c[2][1]+" Q"+c[1][0]+","+c[1][1]+" "+c[0][0]+","+c[0][1]:"M"+c[0][0]+","+c[0][1]+" L"+c[1][0]+","+c[1][1]+" L"+c[1][0]+","+c[1][1]+" L"+c[0][0]+","+c[0][1],e=this._getColor("solid"===this.fillType?b>0?b-1:b:b),a.attr("d",d).attr("fill",e)},d.prototype._getSectionData=function(a){return[{index:a,label:this.data[a][0],value:this._isArray(this.data[a][1])?this.data[a][1][0]:this.data[a][1],formattedValue:this._isArray(this.data[a][1])?this.data[a][1][1]:this.data[a][1].toLocaleString(),baseColor:this.data[a][2],fill:this._getColor(a)}]},d.prototype._getColor=function(a){return"solid"===this.fillType?this.data[a][2]:"url(#gradient-"+a+")"},d.prototype._getPathDefinition=function(a){for(var b="",c=[],d=this.sectionPaths[a],e=0;e<d.length;e++)c=d[e],b+=c[2]+c[0]+","+c[1]+" ";return b},d.prototype._onMouseOver=function(a){b.select(this).attr("fill",c(a.baseColor,-.2))},d.prototype._onMouseOut=function(a){b.select(this).attr("fill",a.fill)},d.prototype._addSectionLabel=function(a,b){var c=b,d=this.sectionPaths[b],e=this._getSectionData(b)[0],f=e.label+": "+e.formattedValue,g=this.data[c][3]||this.label.fill,h=this.width/2,i=this.isCurved?(d[2][1]+d[3][1])/2+this.curveHeight/this.data.length:(d[1][1]+d[2][1])/2;a.append("text").text(f).attr({x:h,y:i,"text-anchor":"middle","dominant-baseline":"middle",fill:g,"pointer-events":"none"}).style("font-size",this.label.fontSize)},d.prototype._isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)},d.prototype._extend=function(a,b){var c;for(c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},a.D3Funnel=d}(window,d3);
/*! d3-funnel - v0.6.10 | 2015 */
"use strict";function _classCallCheck(t,i){if(!(t instanceof i))throw new TypeError("Cannot call a class as a function")}var _createClass=function(){function t(t,i){for(var e=0;e<i.length;e++){var h=i[e];h.enumerable=h.enumerable||!1,h.configurable=!0,"value"in h&&(h.writable=!0),Object.defineProperty(t,h.key,h)}}return function(i,e,h){return e&&t(i.prototype,e),h&&t(i,h),i}}();!function(t,i){function e(t){return"[object Array]"===Object.prototype.toString.call(t)}function h(t,i){var e;for(e in i)i.hasOwnProperty(e)&&(t[e]=i[e]);return t}function s(t,i){var e=parseInt(t.slice(1),16),h=0>i?0:255,s=0>i?-1*i:i,a=e>>16,n=e>>8&255,o=255&e,r=16777216+65536*(Math.round((h-a)*s)+a)+256*(Math.round((h-n)*s)+n)+(Math.round((h-o)*s)+o);return"#"+r.toString(16).slice(1)}var a=function(){function t(i){_classCallCheck(this,t),this.selector=i,this.defaults={width:350,height:400,bottomWidth:1/3,bottomPinch:0,isCurved:!1,curveHeight:20,fillType:"solid",isInverted:!1,hoverEffects:!1,dynamicArea:!1,minHeight:!1,animation:!1,label:{fontSize:"14px",fill:"#fff"}}}return _createClass(t,[{key:"draw",value:function(t,e){this._initialize(t,e),i.select(this.selector).selectAll("svg").remove(),this.svg=i.select(this.selector).append("svg").attr("width",this.width).attr("height",this.height),this.sectionPaths=this._makePaths(),"gradient"===this.fillType&&this._defineColorGradients(this.svg),this.isCurved&&this._drawTopOval(this.svg,this.sectionPaths),this._drawSection(0)}},{key:"_initialize",value:function(t,s){if(!e(t)||0===t.length||!e(t[0])||t[0].length<2)throw{name:"D3 Funnel Data Error",message:"Funnel data is not valid."};s="undefined"!=typeof s?s:{},this.data=t;var a,n=h({},this.defaults);n.width=parseInt(i.select(this.selector).style("width"),10),n.height=parseInt(i.select(this.selector).style("height"),10);var o=Object.keys(s);for(a=0;a<o.length;a++)"label"!==o[a]&&(n[o[a]]=s[o[a]]);if(s.hasOwnProperty("label")){var r,l=/fontSize|fill/;for(r in s.label)r.match(l)&&(n.label[r]=s.label[r])}this.label=n.label,n.width<=0&&(n.width=this.defaults.width),n.height<=0&&(n.height=this.defaults.height);var d=i.scale.category10();for(a=0;a<this.data.length;a++){var c=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i;"2"in this.data[a]&&c.test(this.data[a][2])||(this.data[a][2]=d(a))}this.width=n.width,this.height=n.height,this.bottomWidth=n.width*n.bottomWidth,this.bottomPinch=n.bottomPinch,this.isCurved=n.isCurved,this.curveHeight=n.curveHeight,this.fillType=n.fillType,this.isInverted=n.isInverted,this.hoverEffects=n.hoverEffects,this.dynamicArea=n.dynamicArea,this.minHeight=n.minHeight,this.animation=n.animation,this.bottomLeftX=(this.width-this.bottomWidth)/2,this.dx=this.bottomPinch>0?this.bottomLeftX/(t.length-this.bottomPinch):this.bottomLeftX/t.length,this.dy=this.isCurved?(this.height-this.curveHeight)/t.length:this.height/t.length,this.onItemClick=n.onItemClick}},{key:"_makePaths",value:function(){var t=[],i=this.dx,h=this.dy,s=0,a=this.width,n=0;this.isInverted&&(s=this.bottomLeftX,a=this.width-this.bottomLeftX);var o=0,r=0,l=0,d=this.width/2;this.isCurved&&(n=10);var c=this.width,f=0,u=this.height*(this.width+this.bottomWidth)/2,v=2*this.height/(this.width-this.bottomWidth);if(this.minHeight!==!1){var g=this.height-this.minHeight*this.data.length;u=g*(this.width+this.bottomWidth)/2}for(var m=0,b=0,p=0;p<this.data.length;p++)m+=e(this.data[p][1])?this.data[p][1][0]:this.data[p][1];for(p=0;p<this.data.length;p++){if(b=e(this.data[p][1])?this.data[p][1][0]:this.data[p][1],this.dynamicArea){var y=b/m,w=y*u;this.minHeight!==!1&&(w+=this.minHeight*(this.width+this.bottomWidth)/2),f=Math.sqrt((v*c*c-4*w)/v),i=c/2-f/2,h=2*w/(c+f),this.isCurved&&(h-=this.curveHeight/this.data.length),c=f}this.bottomPinch>0&&(this.isInverted?(this.dynamicArea||(i=this.dx),i=p<this.bottomPinch?0:i):p>=this.data.length-this.bottomPinch&&(i=0)),o=s+i,r=a-i,l=n+h,this.isInverted&&(o=s-i,r=a+i),t.push(this.isCurved?[[s,n,"M"],[d,n+(this.curveHeight-10),"Q"],[a,n,""],[r,l,"L"],[r,l,"M"],[d,l+this.curveHeight,"Q"],[o,l,""],[s,n,"L"]]:[[s,n,"M"],[a,n,"L"],[r,l,"L"],[o,l,"L"],[s,n,"L"]]),s=o,a=r,n=l}return t}},{key:"_defineColorGradients",value:function(t){for(var i=t.append("defs"),e=0;e<this.data.length;e++)for(var h=this.data[e][2],a=s(h,-.25),n=i.append("linearGradient").attr({id:"gradient-"+e}),o=[[0,a],[40,h],[60,h],[100,a]],r=0;r<o.length;r++){var l=o[r];n.append("stop").attr({offset:l[0]+"%",style:"stop-color:"+l[1]})}}},{key:"_drawTopOval",value:function(t,i){var e=0,h=this.width,a=this.width/2;this.isInverted&&(e=this.bottomLeftX,h=this.width-this.bottomLeftX);var n=i[0],o="M"+e+","+n[0][1]+" Q"+a+","+(n[1][1]+this.curveHeight-10)+" "+h+","+n[2][1]+" M"+h+",10 Q"+a+",0 "+e+",10";t.append("path").attr("fill",s(this.data[0][2],-.4)).attr("d",o)}},{key:"_drawSection",value:function(t){if(t!==this.data.length){var i=this.svg.append("g"),e=this._getSectionPath(i,t);if(e.data(this._getSectionData(t)),this.animation!==!1){var h=this;e.transition().duration(this.animation).ease("linear").attr("fill",this._getColor(t)).attr("d",this._getPathDefinition(t)).each("end",function(){h._drawSection(t+1)})}else e.attr("fill",this._getColor(t)).attr("d",this._getPathDefinition(t)),this._drawSection(t+1);this.hoverEffects&&e.on("mouseover",this._onMouseOver).on("mouseout",this._onMouseOut),this.onItemClick&&e.on("click",this.onItemClick),this._addSectionLabel(i,t)}}},{key:"_getSectionPath",value:function(t,i){var e=t.append("path");return this.animation!==!1&&this._addBeforeTransition(e,i),e}},{key:"_addBeforeTransition",value:function(t,i){var e=this.sectionPaths[i],h="",s="";h=this.isCurved?"M"+e[0][0]+","+e[0][1]+" Q"+e[1][0]+","+e[1][1]+" "+e[2][0]+","+e[2][1]+" L"+e[2][0]+","+e[2][1]+" M"+e[2][0]+","+e[2][1]+" Q"+e[1][0]+","+e[1][1]+" "+e[0][0]+","+e[0][1]:"M"+e[0][0]+","+e[0][1]+" L"+e[1][0]+","+e[1][1]+" L"+e[1][0]+","+e[1][1]+" L"+e[0][0]+","+e[0][1],s=this._getColor("solid"===this.fillType?i>0?i-1:i:i),t.attr("d",h).attr("fill",s)}},{key:"_getSectionData",value:function(t){return[{index:t,label:this.data[t][0],value:e(this.data[t][1])?this.data[t][1][0]:this.data[t][1],formattedValue:e(this.data[t][1])?this.data[t][1][1]:this.data[t][1].toLocaleString(),baseColor:this.data[t][2],fill:this._getColor(t)}]}},{key:"_getColor",value:function(t){return"solid"===this.fillType?this.data[t][2]:"url(#gradient-"+t+")"}},{key:"_getPathDefinition",value:function(t){for(var i="",e=[],h=this.sectionPaths[t],s=0;s<h.length;s++)e=h[s],i+=e[2]+e[0]+","+e[1]+" ";return i}},{key:"_onMouseOver",value:function(t){i.select(this).attr("fill",s(t.baseColor,-.2))}},{key:"_onMouseOut",value:function(t){i.select(this).attr("fill",t.fill)}},{key:"_addSectionLabel",value:function(t,i){var e=i,h=this.sectionPaths[i],s=this._getSectionData(i)[0],a=s.label+": "+s.formattedValue,n=this.data[e][3]||this.label.fill,o=this.width/2,r=this.isCurved?(h[2][1]+h[3][1])/2+this.curveHeight/this.data.length:(h[1][1]+h[2][1])/2;t.append("text").text(a).attr({x:o,y:r,"text-anchor":"middle","dominant-baseline":"middle",fill:n,"pointer-events":"none"}).style("font-size",this.label.fontSize)}}]),t}();t.D3Funnel=a}(window,d3);
{
"name": "d3-funnel",
"version": "0.6.9",
"version": "0.6.10",
"description": "A library for rendering SVG funnel charts using D3.js",

@@ -14,7 +14,9 @@ "author": "Jake Zatecky",

"devDependencies": {
"grunt": "~0.4.5",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-concat": "~0.5.0",
"grunt-contrib-uglify": "~0.5.0",
"grunt-jscs": "^1.8.0"
"gulp": "^3.9.0",
"gulp-babel": "^5.1.0",
"gulp-header": "^1.2.2",
"gulp-jscs": "^1.6.0",
"gulp-jshint": "^1.11.0",
"gulp-rename": "^1.2.2",
"gulp-uglify": "^1.2.0"
},

@@ -21,0 +23,0 @@ "dependencies": {

@@ -7,505 +7,505 @@ (function(global, d3) {

/**
* D3Funnel
*
* @param {string} selector A selector for the container element.
*
* @return {void}
*/
var D3Funnel = function(selector)
class D3Funnel
{
this.selector = selector;
// Default configuration values
this.defaults = {
width: 350,
height: 400,
bottomWidth: 1 / 3,
bottomPinch: 0,
isCurved: false,
curveHeight: 20,
fillType: 'solid',
isInverted: false,
hoverEffects: false,
dynamicArea: false,
minHeight: false,
animation: false,
label: {
fontSize: '14px',
fill: '#fff'
}
};
};
/**
* @param {string} selector A selector for the container element.
*
* @return {void}
*/
constructor(selector)
{
this.selector = selector;
/**
* Draw the chart inside the container with the data and configuration
* specified. This will remove any previous SVG elements in the container
* and draw a new funnel chart on top of it.
*
* @param {array} data A list of rows containing a category, a count,
* and optionally a color (in hex).
* @param {Object} options An optional configuration object to override
* defaults. See the docs.
*
* @return {void}
*/
D3Funnel.prototype.draw = function(data, options)
{
// Initialize chart options
this._initialize(data, options);
// Default configuration values
this.defaults = {
width: 350,
height: 400,
bottomWidth: 1 / 3,
bottomPinch: 0,
isCurved: false,
curveHeight: 20,
fillType: 'solid',
isInverted: false,
hoverEffects: false,
dynamicArea: false,
minHeight: false,
animation: false,
label: {
fontSize: '14px',
fill: '#fff'
}
};
}
// Remove any previous drawings
d3.select(this.selector).selectAll('svg').remove();
/**
* Draw the chart inside the container with the data and configuration
* specified. This will remove any previous SVG elements in the container
* and draw a new funnel chart on top of it.
*
* @param {Array} data A list of rows containing a category, a count,
* and optionally a color (in hex).
* @param {Object} options An optional configuration object to override
* defaults. See the docs.
*
* @return {void}
*/
draw(data, options)
{
// Initialize chart options
this._initialize(data, options);
// Add the SVG
this.svg = d3.select(this.selector)
.append('svg')
.attr('width', this.width)
.attr('height', this.height);
// Remove any previous drawings
d3.select(this.selector).selectAll('svg').remove();
this.sectionPaths = this._makePaths();
// Add the SVG
this.svg = d3.select(this.selector)
.append('svg')
.attr('width', this.width)
.attr('height', this.height);
// Define color gradients
if (this.fillType === 'gradient') {
this._defineColorGradients(this.svg);
}
this.sectionPaths = this._makePaths();
// Add top oval if curved
if (this.isCurved) {
this._drawTopOval(this.svg, this.sectionPaths);
}
// Define color gradients
if (this.fillType === 'gradient') {
this._defineColorGradients(this.svg);
}
// Add each block section
this._drawSection(0);
};
// Add top oval if curved
if (this.isCurved) {
this._drawTopOval(this.svg, this.sectionPaths);
}
/**
* Initialize and calculate important variables for drawing the chart.
*
* @param {array} data
* @param {Object} options
*
* @return {void}
*/
D3Funnel.prototype._initialize = function(data, options)
{
if (!this._isArray(data) || data.length === 0 ||
!this._isArray(data[0]) || data[0].length < 2) {
throw {
name: 'D3 Funnel Data Error',
message: 'Funnel data is not valid.'
};
// Add each block section
this._drawSection(0);
}
// Initialize options if not set
options = typeof options !== 'undefined' ? options : {};
/**
* Initialize and calculate important variables for drawing the chart.
*
* @param {Array} data
* @param {Object} options
*
* @return {void}
*/
_initialize(data, options)
{
if (!isArray(data) || data.length === 0 || !isArray(data[0]) || data[0].length < 2) {
throw {
name: 'D3 Funnel Data Error',
message: 'Funnel data is not valid.'
};
}
this.data = data;
// Initialize options if not set
options = typeof options !== 'undefined' ? options : {};
// Counter
var i = 0;
this.data = data;
// Prepare the configuration settings based on the defaults
// Set the default width and height based on the container
var settings = this._extend({}, this.defaults);
settings.width = parseInt(d3.select(this.selector).style('width'), 10);
settings.height = parseInt(d3.select(this.selector).style('height'), 10);
// Counter
var i;
// Overwrite default settings with user options
var keys = Object.keys(options);
for (i = 0; i < keys.length; i++) {
if (keys[i] !== 'label') {
settings[keys[i]] = options[keys[i]];
// Prepare the configuration settings based on the defaults
// Set the default width and height based on the container
var settings = extend({}, this.defaults);
settings.width = parseInt(d3.select(this.selector).style('width'), 10);
settings.height = parseInt(d3.select(this.selector).style('height'), 10);
// Overwrite default settings with user options
var keys = Object.keys(options);
for (i = 0; i < keys.length; i++) {
if (keys[i] !== 'label') {
settings[keys[i]] = options[keys[i]];
}
}
}
// Label settings
if ('label' in options) {
var validLabelOptions = /fontSize|fill/;
var labelOption;
for (labelOption in options.label) {
if (labelOption.match(validLabelOptions)) {
settings.label[labelOption] = options.label[labelOption];
// Label settings
if (options.hasOwnProperty('label')) {
var validLabelOptions = /fontSize|fill/;
var labelOption;
for (labelOption in options.label) {
if (labelOption.match(validLabelOptions)) {
settings.label[labelOption] = options.label[labelOption];
}
}
}
}
this.label = settings.label;
this.label = settings.label;
// In the case that the width or height is not valid, set
// the width/height as its default hard-coded value
if (settings.width <= 0) {
settings.width = this.defaults.width;
}
if (settings.height <= 0) {
settings.height = this.defaults.height;
}
// In the case that the width or height is not valid, set
// the width/height as its default hard-coded value
if (settings.width <= 0) {
settings.width = this.defaults.width;
}
if (settings.height <= 0) {
settings.height = this.defaults.height;
}
// Initialize the colors for each block section
var colorScale = d3.scale.category10();
for (i = 0; i < this.data.length; i++) {
var hexExpression = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
// Initialize the colors for each block section
var colorScale = d3.scale.category10();
for (i = 0; i < this.data.length; i++) {
var hexExpression = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
// If a color is not set for the record, add one
if (!('2' in this.data[i]) || !hexExpression.test(this.data[i][2])) {
this.data[i][2] = colorScale(i);
// If a color is not set for the record, add one
if (!('2' in this.data[i]) || !hexExpression.test(this.data[i][2])) {
this.data[i][2] = colorScale(i);
}
}
}
// Initialize funnel chart settings
this.width = settings.width;
this.height = settings.height;
this.bottomWidth = settings.width * settings.bottomWidth;
this.bottomPinch = settings.bottomPinch;
this.isCurved = settings.isCurved;
this.curveHeight = settings.curveHeight;
this.fillType = settings.fillType;
this.isInverted = settings.isInverted;
this.hoverEffects = settings.hoverEffects;
this.dynamicArea = settings.dynamicArea;
this.minHeight = settings.minHeight;
this.animation = settings.animation;
// Initialize funnel chart settings
this.width = settings.width;
this.height = settings.height;
this.bottomWidth = settings.width * settings.bottomWidth;
this.bottomPinch = settings.bottomPinch;
this.isCurved = settings.isCurved;
this.curveHeight = settings.curveHeight;
this.fillType = settings.fillType;
this.isInverted = settings.isInverted;
this.hoverEffects = settings.hoverEffects;
this.dynamicArea = settings.dynamicArea;
this.minHeight = settings.minHeight;
this.animation = settings.animation;
// Calculate the bottom left x position
this.bottomLeftX = (this.width - this.bottomWidth) / 2;
// Calculate the bottom left x position
this.bottomLeftX = (this.width - this.bottomWidth) / 2;
// Change in x direction
// Will be sharper if there is a pinch
this.dx = this.bottomPinch > 0 ?
// Change in x direction
// Will be sharper if there is a pinch
this.dx = this.bottomPinch > 0 ?
this.bottomLeftX / (data.length - this.bottomPinch) :
this.bottomLeftX / data.length;
// Change in y direction
// Curved chart needs reserved pixels to account for curvature
this.dy = this.isCurved ?
// Change in y direction
// Curved chart needs reserved pixels to account for curvature
this.dy = this.isCurved ?
(this.height - this.curveHeight) / data.length :
this.height / data.length;
// Support for events
this.onItemClick = settings.onItemClick;
};
// Support for events
this.onItemClick = settings.onItemClick;
}
/**
* Create the paths to be used to define the discrete funnel sections and
* returns the results in an array.
*
* @return {array}
*/
D3Funnel.prototype._makePaths = function()
{
var paths = [];
/**
* Create the paths to be used to define the discrete funnel sections and
* returns the results in an array.
*
* @return {Array}
*/
_makePaths()
{
var paths = [];
// Initialize velocity
var dx = this.dx;
var dy = this.dy;
// Initialize velocity
var dx = this.dx;
var dy = this.dy;
// Initialize starting positions
var prevLeftX = 0;
var prevRightX = this.width;
var prevHeight = 0;
// Initialize starting positions
var prevLeftX = 0;
var prevRightX = this.width;
var prevHeight = 0;
// Start from the bottom for inverted
if (this.isInverted) {
prevLeftX = this.bottomLeftX;
prevRightX = this.width - this.bottomLeftX;
}
// Start from the bottom for inverted
if (this.isInverted) {
prevLeftX = this.bottomLeftX;
prevRightX = this.width - this.bottomLeftX;
}
// Initialize next positions
var nextLeftX = 0;
var nextRightX = 0;
var nextHeight = 0;
// Initialize next positions
var nextLeftX = 0;
var nextRightX = 0;
var nextHeight = 0;
var middle = this.width / 2;
var middle = this.width / 2;
// Move down if there is an initial curve
if (this.isCurved) {
prevHeight = 10;
}
// Move down if there is an initial curve
if (this.isCurved) {
prevHeight = 10;
}
var topBase = this.width;
var bottomBase = 0;
var topBase = this.width;
var bottomBase = 0;
var totalArea = this.height * (this.width + this.bottomWidth) / 2;
var slope = 2 * this.height / (this.width - this.bottomWidth);
var totalArea = this.height * (this.width + this.bottomWidth) / 2;
var slope = 2 * this.height / (this.width - this.bottomWidth);
// This is greedy in that the section will have a guranteed height and
// the remaining is shared among the ratio, instead of being shared
// according to the remaining minus the guranteed
if (this.minHeight !== false) {
var height = (this.height - this.minHeight * this.data.length);
totalArea = height * (this.width + this.bottomWidth) / 2;
}
// This is greedy in that the section will have a guranteed height and
// the remaining is shared among the ratio, instead of being shared
// according to the remaining minus the guranteed
if (this.minHeight !== false) {
var height = (this.height - this.minHeight * this.data.length);
totalArea = height * (this.width + this.bottomWidth) / 2;
}
var totalCount = 0;
var count = 0;
var totalCount = 0;
var count = 0;
// Harvest total count
for (var i = 0; i < this.data.length; i++) {
totalCount += this._isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
}
// Harvest total count
for (var i = 0; i < this.data.length; i++) {
totalCount += isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
}
// Create the path definition for each funnel section
// Remember to loop back to the beginning point for a closed path
for (i = 0; i < this.data.length; i++) {
count = this._isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
// Create the path definition for each funnel section
// Remember to loop back to the beginning point for a closed path
for (i = 0; i < this.data.length; i++) {
count = isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
// Calculate dynamic shapes based on area
if (this.dynamicArea) {
var ratio = count / totalCount;
var area = ratio * totalArea;
// Calculate dynamic shapes based on area
if (this.dynamicArea) {
var ratio = count / totalCount;
var area = ratio * totalArea;
if (this.minHeight !== false) {
area += this.minHeight * (this.width + this.bottomWidth) / 2;
}
if (this.minHeight !== false) {
area += this.minHeight * (this.width + this.bottomWidth) / 2;
}
bottomBase = Math.sqrt((slope * topBase * topBase - (4 * area)) / slope);
dx = (topBase / 2) - (bottomBase / 2);
dy = (area * 2) / (topBase + bottomBase);
bottomBase = Math.sqrt((slope * topBase * topBase - (4 * area)) / slope);
dx = (topBase / 2) - (bottomBase / 2);
dy = (area * 2) / (topBase + bottomBase);
if (this.isCurved) {
dy = dy - (this.curveHeight / this.data.length);
if (this.isCurved) {
dy = dy - (this.curveHeight / this.data.length);
}
topBase = bottomBase;
}
topBase = bottomBase;
}
// Stop velocity for pinched sections
if (this.bottomPinch > 0) {
// Check if we've reached the bottom of the pinch
// If so, stop changing on x
if (!this.isInverted) {
if (i >= this.data.length - this.bottomPinch) {
dx = 0;
}
// Pinch at the first sections relating to the bottom pinch
// Revert back to normal velocity after pinch
} else {
// Revert velocity back to the intial if we are using
// static area's (prevents zero velocity if isInverted
// and bottomPinch are non trivial and dynamicArea is false)
if (!this.dynamicArea) {
dx = this.dx;
}
// Stop velocity for pinched sections
if (this.bottomPinch > 0) {
// Check if we've reached the bottom of the pinch
// If so, stop changing on x
if (!this.isInverted) {
if (i >= this.data.length - this.bottomPinch) {
dx = 0;
dx = i < this.bottomPinch ? 0 : dx;
}
// Pinch at the first sections relating to the bottom pinch
// Revert back to normal velocity after pinch
} else {
// Revert velocity back to the intial if we are using
// static area's (prevents zero velocity if isInverted
// and bottomPinch are non trivial and dynamicArea is false)
if (!this.dynamicArea) {
dx = this.dx;
}
}
dx = i < this.bottomPinch ? 0 : dx;
// Calculate the position of next section
nextLeftX = prevLeftX + dx;
nextRightX = prevRightX - dx;
nextHeight = prevHeight + dy;
// Expand outward if inverted
if (this.isInverted) {
nextLeftX = prevLeftX - dx;
nextRightX = prevRightX + dx;
}
}
// Calculate the position of next section
nextLeftX = prevLeftX + dx;
nextRightX = prevRightX - dx;
nextHeight = prevHeight + dy;
// Plot curved lines
if (this.isCurved) {
paths.push([
// Top Bezier curve
[prevLeftX, prevHeight, 'M'],
[middle, prevHeight + (this.curveHeight - 10), 'Q'],
[prevRightX, prevHeight, ''],
// Right line
[nextRightX, nextHeight, 'L'],
// Bottom Bezier curve
[nextRightX, nextHeight, 'M'],
[middle, nextHeight + this.curveHeight, 'Q'],
[nextLeftX, nextHeight, ''],
// Left line
[prevLeftX, prevHeight, 'L']
]);
// Plot straight lines
} else {
paths.push([
// Start position
[prevLeftX, prevHeight, 'M'],
// Move to right
[prevRightX, prevHeight, 'L'],
// Move down
[nextRightX, nextHeight, 'L'],
// Move to left
[nextLeftX, nextHeight, 'L'],
// Wrap back to top
[prevLeftX, prevHeight, 'L']
]);
}
// Expand outward if inverted
if (this.isInverted) {
nextLeftX = prevLeftX - dx;
nextRightX = prevRightX + dx;
// Set the next section's previous position
prevLeftX = nextLeftX;
prevRightX = nextRightX;
prevHeight = nextHeight;
}
// Plot curved lines
if (this.isCurved) {
paths.push([
// Top Bezier curve
[prevLeftX, prevHeight, 'M'],
[middle, prevHeight + (this.curveHeight - 10), 'Q'],
[prevRightX, prevHeight, ''],
// Right line
[nextRightX, nextHeight, 'L'],
// Bottom Bezier curve
[nextRightX, nextHeight, 'M'],
[middle, nextHeight + this.curveHeight, 'Q'],
[nextLeftX, nextHeight, ''],
// Left line
[prevLeftX, prevHeight, 'L']
]);
// Plot straight lines
} else {
paths.push([
// Start position
[prevLeftX, prevHeight, 'M'],
// Move to right
[prevRightX, prevHeight, 'L'],
// Move down
[nextRightX, nextHeight, 'L'],
// Move to left
[nextLeftX, nextHeight, 'L'],
// Wrap back to top
[prevLeftX, prevHeight, 'L']
]);
}
// Set the next section's previous position
prevLeftX = nextLeftX;
prevRightX = nextRightX;
prevHeight = nextHeight;
return paths;
}
return paths;
};
/**
* Define the linear color gradients.
*
* @param {Object} svg
*
* @return {void}
*/
_defineColorGradients(svg)
{
var defs = svg.append('defs');
/**
* Define the linear color gradients.
*
* @param {Object} svg
*
* @return {void}
*/
D3Funnel.prototype._defineColorGradients = function(svg)
{
var defs = svg.append('defs');
// Create a gradient for each section
for (var i = 0; i < this.data.length; i++) {
var color = this.data[i][2];
var shade = shadeColor(color, -0.25);
// Create a gradient for each section
for (var i = 0; i < this.data.length; i++) {
var color = this.data[i][2];
var shade = shadeColor(color, -0.25);
// Create linear gradient
var gradient = defs.append('linearGradient')
.attr({
id: 'gradient-' + i
});
// Create linear gradient
var gradient = defs.append('linearGradient')
.attr({
id: 'gradient-' + i
});
// Define the gradient stops
var stops = [
[0, shade],
[40, color],
[60, color],
[100, shade]
];
// Define the gradient stops
var stops = [
[0, shade],
[40, color],
[60, color],
[100, shade]
];
// Add the gradient stops
for (var j = 0; j < stops.length; j++) {
var stop = stops[j];
gradient.append('stop').attr({
offset: stop[0] + '%',
style: 'stop-color:' + stop[1]
});
// Add the gradient stops
for (var j = 0; j < stops.length; j++) {
var stop = stops[j];
gradient.append('stop').attr({
offset: stop[0] + '%',
style: 'stop-color:' + stop[1]
});
}
}
}
};
/**
* Draw the top oval of a curved funnel.
*
* @param {Object} svg
* @param {array} sectionPaths
*
* @return {void}
*/
D3Funnel.prototype._drawTopOval = function(svg, sectionPaths)
{
var leftX = 0;
var rightX = this.width;
var centerX = this.width / 2;
/**
* Draw the top oval of a curved funnel.
*
* @param {Object} svg
* @param {Array} sectionPaths
*
* @return {void}
*/
_drawTopOval(svg, sectionPaths)
{
var leftX = 0;
var rightX = this.width;
var centerX = this.width / 2;
if (this.isInverted) {
leftX = this.bottomLeftX;
rightX = this.width - this.bottomLeftX;
if (this.isInverted) {
leftX = this.bottomLeftX;
rightX = this.width - this.bottomLeftX;
}
// Create path form top-most section
var paths = sectionPaths[0];
var path = 'M' + leftX + ',' + paths[0][1] +
' Q' + centerX + ',' + (paths[1][1] + this.curveHeight - 10) +
' ' + rightX + ',' + paths[2][1] +
' M' + rightX + ',10' +
' Q' + centerX + ',0' +
' ' + leftX + ',10';
// Draw top oval
svg.append('path')
.attr('fill', shadeColor(this.data[0][2], -0.4))
.attr('d', path);
}
// Create path form top-most section
var paths = sectionPaths[0];
var path = 'M' + leftX + ',' + paths[0][1] +
' Q' + centerX + ',' + (paths[1][1] + this.curveHeight - 10) +
' ' + rightX + ',' + paths[2][1] +
' M' + rightX + ',10' +
' Q' + centerX + ',0' +
' ' + leftX + ',10';
/**
* Draw the next section in the iteration.
*
* @param {int} index
*
* @return {void}
*/
_drawSection(index)
{
if (index === this.data.length) {
return;
}
// Draw top oval
svg.append('path')
.attr('fill', shadeColor(this.data[0][2], -0.4))
.attr('d', path);
};
// Create a group just for this block
var group = this.svg.append('g');
/**
* Draw the next section in the iteration.
*
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._drawSection = function(index)
{
if (index === this.data.length) {
return;
}
// Fetch path element
var path = this._getSectionPath(group, index);
path.data(this._getSectionData(index));
// Create a group just for this block
var group = this.svg.append('g');
// Add animation components
if (this.animation !== false) {
var self = this;
path.transition()
.duration(this.animation)
.ease('linear')
.attr('fill', this._getColor(index))
.attr('d', this._getPathDefinition(index))
.each('end', function () {
self._drawSection(index + 1);
});
} else {
path.attr('fill', this._getColor(index))
.attr('d', this._getPathDefinition(index));
this._drawSection(index + 1);
}
// Fetch path element
var path = this._getSectionPath(group, index);
path.data(this._getSectionData(index));
// Add the hover events
if (this.hoverEffects) {
path.on('mouseover', this._onMouseOver)
.on('mouseout', this._onMouseOut);
}
// Add animation components
if (this.animation !== false) {
var self = this;
path.transition()
.duration(this.animation)
.ease('linear')
.attr('fill', this._getColor(index))
.attr('d', this._getPathDefinition(index))
.each('end', function() {
self._drawSection(index + 1);
});
} else {
path.attr('fill', this._getColor(index))
.attr('d', this._getPathDefinition(index));
this._drawSection(index + 1);
}
// ItemClick event
if (this.onItemClick) {
path.on('click', this.onItemClick);
}
// Add the hover events
if (this.hoverEffects) {
path.on('mouseover', this._onMouseOver)
.on('mouseout', this._onMouseOut);
this._addSectionLabel(group, index);
}
// ItemClick event
if (this.onItemClick) {
path.on('click', this.onItemClick);
}
/**
* @param {Object} group
* @param {int} index
*
* @return {Object}
*/
_getSectionPath(group, index)
{
var path = group.append('path');
this._addSectionLabel(group, index);
};
if (this.animation !== false) {
this._addBeforeTransition(path, index);
}
/**
* @param {Object} group
* @param {int} index
*
* @return {Object}
*/
D3Funnel.prototype._getSectionPath = function(group, index)
{
var path = group.append('path');
if (this.animation !== false) {
this._addBeforeTransition(path, index);
return path;
}
return path;
};
/**
* Set the attributes of a path element before its animation.
*
* @param {Object} path
* @param {int} index
*
* @return {void}
*/
_addBeforeTransition(path, index)
{
var paths = this.sectionPaths[index];
/**
* Set the attributes of a path element before its animation.
*
* @param {Object} path
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._addBeforeTransition = function(path, index)
{
var paths = this.sectionPaths[index];
var beforePath = '';
var beforeFill = '';
var beforePath = '';
var beforeFill = '';
// Construct the top of the trapezoid and leave the other elements
// hovering around to expand downward on animation
if (!this.isCurved) {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] +
// Construct the top of the trapezoid and leave the other elements
// hovering around to expand downward on animation
if (!this.isCurved) {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] +
' L' + paths[1][0] + ',' + paths[1][1] +
' L' + paths[1][0] + ',' + paths[1][1] +
' L' + paths[0][0] + ',' + paths[0][1];
} else {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] +
} else {
beforePath = 'M' + paths[0][0] + ',' + paths[0][1] +
' Q' + paths[1][0] + ',' + paths[1][1] +

@@ -517,131 +517,132 @@ ' ' + paths[2][0] + ',' + paths[2][1] +

' ' + paths[0][0] + ',' + paths[0][1];
}
// Use previous fill color, if available
if (this.fillType === 'solid') {
beforeFill = index > 0 ? this._getColor(index - 1) : this._getColor(index);
// Use current background if gradient (gradients do not transition)
} else {
beforeFill = this._getColor(index);
}
path.attr('d', beforePath)
.attr('fill', beforeFill);
}
// Use previous fill color, if available
if (this.fillType === 'solid') {
beforeFill = index > 0 ? this._getColor(index - 1) : this._getColor(index);
// Use current background if gradient (gradients do not transition)
} else {
beforeFill = this._getColor(index);
/**
* @param {int} index
*
* @return {Array}
*/
_getSectionData(index)
{
return [{
index: index,
label: this.data[index][0],
value: isArray(this.data[index][1]) ? this.data[index][1][0] : this.data[index][1], formattedValue: isArray(this.data[index][1]) ? this.data[index][1][1] : this.data[index][1].toLocaleString(),
baseColor: this.data[index][2],
fill: this._getColor(index)
}];
}
path.attr('d', beforePath)
.attr('fill', beforeFill);
};
/**
* Return the color for the given index.
*
* @param {int} index
*
* @return {string}
*/
_getColor(index)
{
if (this.fillType === 'solid') {
return this.data[index][2];
} else {
return 'url(#gradient-' + index + ')';
}
}
/**
* @param {int} index
*
* @return {array}
*/
D3Funnel.prototype._getSectionData = function(index)
{
return [{
index: index,
label: this.data[index][0],
value: this._isArray(this.data[index][1]) ? this.data[index][1][0] : this.data[index][1], formattedValue: this._isArray(this.data[index][1]) ? this.data[index][1][1] : this.data[index][1].toLocaleString(),
baseColor: this.data[index][2],
fill: this._getColor(index)
}];
};
/**
* @param {int} index
*
* @return {string}
*/
_getPathDefinition(index)
{
var pathStr = '';
var point = [];
var paths = this.sectionPaths[index];
for (var j = 0; j < paths.length; j++) {
point = paths[j];
pathStr += point[2] + point[0] + ',' + point[1] + ' ';
}
/**
* Return the color for the given index.
*
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._getColor = function(index)
{
if (this.fillType === 'solid') {
return this.data[index][2];
} else {
return 'url(#gradient-' + index + ')';
return pathStr;
}
};
/**
* @param {int} index
*
* @return {string}
*/
D3Funnel.prototype._getPathDefinition = function(index)
{
var pathStr = '';
var point = [];
var paths = this.sectionPaths[index];
/**
* @param {Object} data
*
* @return {void}
*/
_onMouseOver(data)
{
d3.select(this).attr('fill', shadeColor(data.baseColor, -0.2));
}
for (var j = 0; j < paths.length; j++) {
point = paths[j];
pathStr += point[2] + point[0] + ',' + point[1] + ' ';
/**
* @param {Object} data
*
* @return {void}
*/
_onMouseOut(data)
{
d3.select(this).attr('fill', data.fill);
}
return pathStr;
};
/**
* @param {Object} group
* @param {int} index
*
* @return {void}
*/
_addSectionLabel(group, index)
{
var i = index;
var paths = this.sectionPaths[index];
var sectionData = this._getSectionData(index)[0];
var textStr = sectionData.label + ': ' + sectionData.formattedValue;
var textFill = this.data[i][3] || this.label.fill;
/**
* @param {Object} data
*
* @return {void}
*/
D3Funnel.prototype._onMouseOver = function(data)
{
d3.select(this).attr('fill', shadeColor(data.baseColor, -0.2));
};
/**
* @param {Object} data
*
* @return {void}
*/
D3Funnel.prototype._onMouseOut = function(data)
{
d3.select(this).attr('fill', data.fill);
};
/**
* @param {Object} group
* @param {int} index
*
* @return {void}
*/
D3Funnel.prototype._addSectionLabel = function(group, index)
{
var i = index;
var paths = this.sectionPaths[index];
var sectionData = this._getSectionData(index)[0];
var textStr = sectionData.label + ': ' + sectionData.formattedValue;
var textFill = this.data[i][3] || this.label.fill;
var textX = this.width / 2; // Center the text
var textY = !this.isCurved ? // Average height of bases
var textX = this.width / 2; // Center the text
var textY = !this.isCurved ? // Average height of bases
(paths[1][1] + paths[2][1]) / 2 :
(paths[2][1] + paths[3][1]) / 2 + (this.curveHeight / this.data.length);
group.append('text')
.text(textStr)
.attr({
'x': textX,
'y': textY,
'text-anchor': 'middle',
'dominant-baseline': 'middle',
'fill': textFill,
'pointer-events': 'none'
})
.style('font-size', this.label.fontSize);
};
group.append('text')
.text(textStr)
.attr({
'x': textX,
'y': textY,
'text-anchor': 'middle',
'dominant-baseline': 'middle',
'fill': textFill,
'pointer-events': 'none'
})
.style('font-size', this.label.fontSize);
}
}
/**
* Check if the supplied value is an array.
*
* @param {mixed} value
* @param {*} value
*
* @return {bool}
*/
D3Funnel.prototype._isArray = function(value)
function isArray(value)
{
return Object.prototype.toString.call(value) === '[object Array]';
};
}

@@ -656,3 +657,3 @@ /**

*/
D3Funnel.prototype._extend = function(a, b)
function extend(a, b)
{

@@ -666,3 +667,3 @@ var prop;

return a;
};
}

@@ -673,5 +674,5 @@ /**

* @param {string} color A hex color.
* @param {float} shade The shade adjustment. Can be positive or negative.
* @param {number} shade The shade adjustment. Can be positive or negative.
*
* @return {void}
* @return {string}
*/

@@ -678,0 +679,0 @@ function shadeColor(color, shade)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc