Comparing version 0.6.9 to 0.6.10
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63127
7
1246