Comparing version 0.7.0 to 0.7.1
@@ -0,1 +1,12 @@ | ||
## v0.7.1 (October 28, 2015) | ||
### Behavior Changes | ||
* Errors thrown on data validation are now more descriptive and context-aware | ||
### Bug Fixes | ||
* [#35]: Fix issue where gradient background would not persist after mouse out | ||
* [#36]: Fix issue where non-SVG entities were not being removed from container | ||
## v0.7.0 (October 4, 2015) | ||
@@ -14,3 +25,3 @@ | ||
* Heights determined by weighted area: http://jsfiddle.net/zq4L82kv/2/ (legacy v0.6.x) | ||
* Heights determined by weighted height: http://jsfiddle.net/bawv6m0j/1/ (v0.7+) | ||
* Heights determined by weighted height: http://jsfiddle.net/bawv6m0j/3/ (v0.7+) | ||
@@ -37,3 +48,3 @@ ### New Features | ||
| `bottomWidth` | `chart.bottomWidth` | | | ||
| `curveHeight | `chart.curve.height` | | | ||
| `curveHeight` | `chart.curve.height` | | | ||
| `dynamicArea` | `block.dynamicHeight` | See change #29. | | ||
@@ -40,0 +51,0 @@ | `fillType` | `block.fill.type` | | |
@@ -90,4 +90,11 @@ | ||
value: function destroy() { | ||
var container = d3.select(this.selector); | ||
// D3's remove method appears to be sufficient for removing the events | ||
d3.select(this.selector).selectAll('svg').remove(); | ||
container.selectAll('svg').remove(); | ||
// Remove other elements from container | ||
container.selectAll('*').remove(); | ||
// Remove inner text from container | ||
container.text(''); | ||
} | ||
@@ -179,5 +186,17 @@ | ||
value: function _validateData(data) { | ||
if (Array.isArray(data) === false || data.length === 0 || Array.isArray(data[0]) === false || data[0].length < 2) { | ||
throw new Error('Funnel data is not valid.'); | ||
if (Array.isArray(data) === false) { | ||
throw new Error('Data must be an array.'); | ||
} | ||
if (data.length === 0) { | ||
throw new Error('Data array must contain at least one element.'); | ||
} | ||
if (Array.isArray(data[0]) === false) { | ||
throw new Error('Data array elements must be arrays.'); | ||
} | ||
if (data[0].length < 2) { | ||
throw new Error('Data array elements must contain a label and value.'); | ||
} | ||
} | ||
@@ -279,3 +298,3 @@ | ||
formatted: _this2.labelFormatter.format(label, count), | ||
fill: _this2.colorizer.getBlockFill(block, index), | ||
fill: _this2.colorizer.getBlockFill(block, index, _this2.fillType), | ||
label: { | ||
@@ -565,4 +584,4 @@ raw: label, | ||
this.blocks.forEach(function (block, index) { | ||
var color = block.fill; | ||
var shade = Colorizer.shade(color, -0.25); | ||
var color = block.fill.raw; | ||
var shade = Colorizer.shade(color, -0.2); | ||
@@ -614,3 +633,3 @@ // Create linear gradient | ||
// Draw top oval | ||
svg.append('path').attr('fill', Colorizer.shade(this.blocks[0].fill, -0.4)).attr('d', path); | ||
svg.append('path').attr('fill', Colorizer.shade(this.blocks[0].fill.raw, -0.4)).attr('d', path); | ||
} | ||
@@ -643,7 +662,7 @@ | ||
if (this.animation !== false) { | ||
path.transition().duration(this.animation).ease('linear').attr('fill', this._getFillColor(index)).attr('d', this._getPathDefinition(index)).each('end', function () { | ||
path.transition().duration(this.animation).ease('linear').attr('fill', this.blocks[index].fill.actual).attr('d', this._getPathDefinition(index)).each('end', function () { | ||
_this4._drawBlock(index + 1); | ||
}); | ||
} else { | ||
path.attr('fill', this._getFillColor(index)).attr('d', this._getPathDefinition(index)); | ||
path.attr('fill', this.blocks[index].fill.actual).attr('d', this._getPathDefinition(index)); | ||
this._drawBlock(index + 1); | ||
@@ -657,3 +676,3 @@ } | ||
// ItemClick event | ||
// Add block click event | ||
if (this.onBlockClick !== null) { | ||
@@ -710,6 +729,6 @@ path.on('click', this.onBlockClick); | ||
if (this.fillType === 'solid' && index > 0) { | ||
beforeFill = this._getFillColor(index - 1); | ||
beforeFill = this.blocks[index - 1].fill.actual; | ||
// Otherwise use current background | ||
} else { | ||
beforeFill = this._getFillColor(index); | ||
beforeFill = this.blocks[index].fill.actual; | ||
} | ||
@@ -734,4 +753,2 @@ | ||
/** | ||
* Return the block fill color for the given index. | ||
* | ||
* @param {int} index | ||
@@ -742,17 +759,2 @@ * | ||
}, { | ||
key: '_getFillColor', | ||
value: function _getFillColor(index) { | ||
if (this.fillType === 'solid') { | ||
return this.blocks[index].fill; | ||
} | ||
return 'url(#gradient-' + index + ')'; | ||
} | ||
/** | ||
* @param {int} index | ||
* | ||
* @return {string} | ||
*/ | ||
}, { | ||
key: '_getPathDefinition', | ||
@@ -777,14 +779,14 @@ value: function _getPathDefinition(index) { | ||
value: function _onMouseOver(data) { | ||
d3.select(this).attr('fill', Colorizer.shade(data.fill, -0.2)); | ||
d3.select(this).attr('fill', Colorizer.shade(data.fill.raw, -0.2)); | ||
} | ||
/** | ||
* @param {Object} data | ||
* | ||
* @return {void} | ||
*/ | ||
* @param {Object} data | ||
* | ||
* @return {void} | ||
*/ | ||
}, { | ||
key: '_onMouseOut', | ||
value: function _onMouseOut(data) { | ||
d3.select(this).attr('fill', data.fill); | ||
d3.select(this).attr('fill', data.fill.actual); | ||
} | ||
@@ -795,3 +797,2 @@ | ||
* @param {int} index | ||
* | ||
* @return {void} | ||
@@ -883,8 +884,28 @@ */ | ||
* @param {Number} index | ||
* @param {string} type | ||
* | ||
* @return {Object} | ||
*/ | ||
}, { | ||
key: 'getBlockFill', | ||
value: function getBlockFill(block, index, type) { | ||
var raw = this.getBlockRawFill(block, index); | ||
return { | ||
raw: raw, | ||
actual: this.getBlockActualFill(raw, index, type) | ||
}; | ||
} | ||
/** | ||
* Return the raw hex color for the block. | ||
* | ||
* @param {Array} block | ||
* @param {Number} index | ||
* | ||
* @return {string} | ||
*/ | ||
}, { | ||
key: 'getBlockFill', | ||
value: function getBlockFill(block, index) { | ||
key: 'getBlockRawFill', | ||
value: function getBlockRawFill(block, index) { | ||
// Use the block's color, if set and valid | ||
@@ -895,2 +916,3 @@ if (block.length > 2 && this.hexExpression.test(block[2])) { | ||
// Otherwise, attempt to use the array scale | ||
if (Array.isArray(this.scale)) { | ||
@@ -900,2 +922,3 @@ return this.scale[index]; | ||
// Finally, use a functional scale | ||
return this.scale(index); | ||
@@ -905,2 +928,21 @@ } | ||
/** | ||
* Return the actual background for the block. | ||
* | ||
* @param {string} raw | ||
* @param {Number} index | ||
* @param {string} type | ||
* | ||
* @return {string} | ||
*/ | ||
}, { | ||
key: 'getBlockActualFill', | ||
value: function getBlockActualFill(raw, index, type) { | ||
if (type === 'solid') { | ||
return raw; | ||
} | ||
return 'url(#gradient-' + index + ')'; | ||
} | ||
/** | ||
* Given a raw data block, return an appropriate label color. | ||
@@ -907,0 +949,0 @@ * |
@@ -1,2 +0,2 @@ | ||
/*! d3-funnel - v0.7.0 | 2015 */ | ||
!function(t,e){"function"==typeof define&&define.amd?define(["d3"],e):"object"==typeof exports?module.exports=e(require("d3")):t.D3Funnel=e(t.d3)}(this,function(t){"use strict";function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var i=function(){function t(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,i,n){return i&&t(e.prototype,i),n&&t(e,n),e}}(),n=function(){function n(t){e(this,n),this.selector=t,this.colorizer=new h,this.labelFormatter=new o,this.navigator=new a}return i(n,null,[{key:"defaults",value:{chart:{width:350,height:400,bottomWidth:1/3,bottomPinch:0,inverted:!1,animate:!1,curve:{enabled:!1,height:20}},block:{dynamicHeight:!1,fill:{scale:t.scale.category10().domain(t.range(0,10)),type:"solid"},minHeight:!1,highlight:!1},label:{fontSize:"14px",fill:"#fff",format:"{l}: {f}"},events:{click:{block:null}}},enumerable:!0}]),i(n,[{key:"destroy",value:function(){t.select(this.selector).selectAll("svg").remove()}},{key:"draw",value:function(t){var e=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];this.destroy(),this._initialize(t,e),this._draw()}},{key:"_initialize",value:function(t,e){this._validateData(t);var i=this._getSettings(e);this.label=i.label,this.labelFormatter.setFormat(this.label.format),this.colorizer.setLabelFill(i.label.fill),this.colorizer.setScale(i.block.fill.scale),this.width=i.chart.width,this.height=i.chart.height,this.bottomWidth=i.chart.width*i.chart.bottomWidth,this.bottomPinch=i.chart.bottomPinch,this.isInverted=i.chart.inverted,this.isCurved=i.chart.curve.enabled,this.curveHeight=i.chart.curve.height,this.fillType=i.block.fill.type,this.hoverEffects=i.block.highlight,this.dynamicHeight=i.block.dynamicHeight,this.minHeight=i.block.minHeight,this.animation=i.chart.animate,this.onBlockClick=i.events.click.block,this._setBlocks(t),this.bottomLeftX=(this.width-this.bottomWidth)/2,this.dx=this._getDx(),this.dy=this._getDy()}},{key:"_validateData",value:function(t){if(Array.isArray(t)===!1||0===t.length||Array.isArray(t[0])===!1||t[0].length<2)throw new Error("Funnel data is not valid.")}},{key:"_getSettings",value:function(e){var i=l.extend({},n.defaults);return i.chart.width=parseInt(t.select(this.selector).style("width"),10),i.chart.height=parseInt(t.select(this.selector).style("height"),10),i=l.extend(i,e),i.chart.width<=0&&(i.chart.width=n.defaults.chart.width),i.chart.height<=0&&(i.chart.height=n.defaults.chart.height),i}},{key:"_setBlocks",value:function(t){var e=this._getTotalCount(t);this.blocks=this._standardizeData(t,e)}},{key:"_getTotalCount",value:function(t){var e=this,i=0;return t.forEach(function(t){i+=e._getRawBlockCount(t)}),i}},{key:"_standardizeData",value:function(t,e){var i=this,n=[],h=void 0,o=void 0,a=void 0;return t.forEach(function(t,l){h=i._getRawBlockCount(t),o=h/e,a=t[0],n.push({index:l,value:h,ratio:o,height:i.height*o,formatted:i.labelFormatter.format(a,h),fill:i.colorizer.getBlockFill(t,l),label:{raw:a,formatted:i.labelFormatter.format(a,h),color:i.colorizer.getLabelFill(t,l)}})}),n}},{key:"_getRawBlockCount",value:function(t){return Array.isArray(t[1])?t[1][0]:t[1]}},{key:"_getDx",value:function(){return this.bottomPinch>0?this.bottomLeftX/(this.blocks.length-this.bottomPinch):this.bottomLeftX/this.blocks.length}},{key:"_getDy",value:function(){return this.isCurved?(this.height-this.curveHeight)/this.blocks.length:this.height/this.blocks.length}},{key:"_draw",value:function(){this.svg=t.select(this.selector).append("svg").attr("width",this.width).attr("height",this.height),this.blockPaths=this._makePaths(),"gradient"===this.fillType&&this._defineColorGradients(this.svg),this.isCurved&&this._drawTopOval(this.svg,this.blockPaths),this._drawBlock(0)}},{key:"_makePaths",value:function(){var t=this,e=[],i=this.dx,n=this.dy,h=0,o=this.width,a=0;this.isInverted&&(h=this.bottomLeftX,o=this.width-this.bottomLeftX);var l=0,s=0,r=0,c=this.width/2;this.isCurved&&(a=10);var u=this.height;this.minHeight!==!1&&(u=this.height-this.minHeight*this.blocks.length);var d=this.height;this.blocks.forEach(function(e,i){t.bottomPinch>0&&(t.isInverted?i<t.bottomPinch&&(d-=e.height):i>=t.blocks.length-t.bottomPinch&&(d-=e.height))});var f=2*d/(this.width-this.bottomWidth);return this.blocks.forEach(function(d,v){t.dynamicHeight&&(n=u*d.ratio,t.minHeight!==!1&&(n+=t.minHeight),t.isCurved&&(n-=t.curveHeight/t.blocks.length),l=(a+n)/f,t.isInverted&&(l=(a+n-t.height)/(-1*f)),0===t.bottomWidth&&v===t.blocks.length-1&&(l=t.width/2,t.isInverted&&(l=0)),t.bottomWidth===t.width&&(l=h),i=l-h,t.isInverted&&(i=h-l)),t.bottomPinch>0&&(t.isInverted?(t.dynamicHeight||(i=t.dx),i=v<t.bottomPinch?0:i):v>=t.blocks.length-t.bottomPinch&&(i=0)),l=h+i,s=o-i,r=a+n,t.isInverted&&(l=h-i,s=o+i),t.isCurved?e.push([[h,a,"M"],[c,a+(t.curveHeight-10),"Q"],[o,a,""],[s,r,"L"],[s,r,"M"],[c,r+t.curveHeight,"Q"],[l,r,""],[h,a,"L"]]):e.push([[h,a,"M"],[o,a,"L"],[s,r,"L"],[l,r,"L"],[h,a,"L"]]),h=l,o=s,a=r}),e}},{key:"_defineColorGradients",value:function(t){var e=t.append("defs");this.blocks.forEach(function(t,i){var n=t.fill,o=h.shade(n,-.25),a=e.append("linearGradient").attr({id:"gradient-"+i}),l=[[0,o],[40,n],[60,n],[100,o]];l.forEach(function(t){a.append("stop").attr({offset:t[0]+"%",style:"stop-color:"+t[1]})})})}},{key:"_drawTopOval",value:function(t,e){var i=0,n=this.width,o=this.width/2;this.isInverted&&(i=this.bottomLeftX,n=this.width-this.bottomLeftX);var a=e[0],l=a[1][1]+this.curveHeight-10,s=this.navigator.plot([["M",i,a[0][1]],["Q",o,l],[" ",n,a[2][1]],["M",n,10],["Q",o,0],[" ",i,10]]);t.append("path").attr("fill",h.shade(this.blocks[0].fill,-.4)).attr("d",s)}},{key:"_drawBlock",value:function(t){var e=this;if(t!==this.blocks.length){var i=this.svg.append("g"),n=this._getBlockPath(i,t);n.data(this._getD3Data(t)),this.animation!==!1?n.transition().duration(this.animation).ease("linear").attr("fill",this._getFillColor(t)).attr("d",this._getPathDefinition(t)).each("end",function(){e._drawBlock(t+1)}):(n.attr("fill",this._getFillColor(t)).attr("d",this._getPathDefinition(t)),this._drawBlock(t+1)),this.hoverEffects&&n.on("mouseover",this._onMouseOver).on("mouseout",this._onMouseOut),null!==this.onBlockClick&&n.on("click",this.onBlockClick),this._addBlockLabel(i,t)}}},{key:"_getBlockPath",value:function(t,e){var i=t.append("path");return this.animation!==!1&&this._addBeforeTransition(i,e),i}},{key:"_addBeforeTransition",value:function(t,e){var i=this.blockPaths[e],n="",h="";n=this.isCurved?this.navigator.plot([["M",i[0][0],i[0][1]],["Q",i[1][0],i[1][1]],[" ",i[2][0],i[2][1]],["L",i[2][0],i[2][1]],["M",i[2][0],i[2][1]],["Q",i[1][0],i[1][1]],[" ",i[0][0],i[0][1]]]):this.navigator.plot([["M",i[0][0],i[0][1]],["L",i[1][0],i[1][1]],["L",i[1][0],i[1][1]],["L",i[0][0],i[0][1]]]),h="solid"===this.fillType&&e>0?this._getFillColor(e-1):this._getFillColor(e),t.attr("d",n).attr("fill",h)}},{key:"_getD3Data",value:function(t){return[this.blocks[t]]}},{key:"_getFillColor",value:function(t){return"solid"===this.fillType?this.blocks[t].fill:"url(#gradient-"+t+")"}},{key:"_getPathDefinition",value:function(t){var e=[];return this.blockPaths[t].forEach(function(t){e.push([t[2],t[0],t[1]])}),this.navigator.plot(e)}},{key:"_onMouseOver",value:function(e){t.select(this).attr("fill",h.shade(e.fill,-.2))}},{key:"_onMouseOut",value:function(e){t.select(this).attr("fill",e.fill)}},{key:"_addBlockLabel",value:function(t,e){var i=this.blockPaths[e],n=this.blocks[e].label.formatted,h=this.blocks[e].label.color,o=this.width/2,a=this._getTextY(i);t.append("text").text(n).attr({x:o,y:a,"text-anchor":"middle","dominant-baseline":"middle",fill:h,"pointer-events":"none"}).style("font-size",this.label.fontSize)}},{key:"_getTextY",value:function(t){return this.isCurved?(t[2][1]+t[3][1])/2+this.curveHeight/this.blocks.length:(t[1][1]+t[2][1])/2}}]),n}(),h=function(){function t(){e(this,t),this.hexExpression=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i,this.labelFill=null,this.scale=null}return i(t,[{key:"setLabelFill",value:function(t){this.labelFill=t}},{key:"setScale",value:function(t){this.scale=t}},{key:"getBlockFill",value:function(t,e){return t.length>2&&this.hexExpression.test(t[2])?t[2]:Array.isArray(this.scale)?this.scale[e]:this.scale(e)}},{key:"getLabelFill",value:function(t){return t.length>3&&this.hexExpression.test(t[3])?t[3]:this.labelFill}}],[{key:"shade",value:function(e,i){var n=e.slice(1);3===n.length&&(n=t.expandHex(n));var h=parseInt(n,16),o=0>i?0:255,a=0>i?-1*i:i,l=h>>16,s=h>>8&255,r=255&h,c=16777216+65536*(Math.round((o-l)*a)+l)+256*(Math.round((o-s)*a)+s)+(Math.round((o-r)*a)+r);return"#"+c.toString(16).slice(1)}},{key:"expandHex",value:function(t){return t[0]+t[0]+t[1]+t[1]+t[2]+t[2]}}]),t}(),o=function(){function t(){e(this,t),this.expression=null}return i(t,[{key:"setFormat",value:function(t){"function"==typeof t?this.formatter=t:(this.expression=t,this.formatter=this.stringFormatter)}},{key:"format",value:function(t,e){return Array.isArray(e)?this.formatter(t,e[0],e[1]):this.formatter(t,e,null)}},{key:"stringFormatter",value:function(t,e){var i=arguments.length<=2||void 0===arguments[2]?null:arguments[2],n=i;return null===i&&(n=this.getDefaultFormattedValue(e)),this.expression.split("{l}").join(t).split("{v}").join(e).split("{f}").join(n)}},{key:"getDefaultFormattedValue",value:function(t){return t.toLocaleString()}}]),t}(),a=function(){function t(){e(this,t)}return i(t,[{key:"plot",value:function(t){var e="";return t.forEach(function(t){e+=t[0]+t[1]+","+t[2]+" "}),e.replace(/ +/g," ").trim()}}]),t}(),l=function(){function t(){e(this,t)}return i(t,null,[{key:"extend",value:function(e,i){var n=void 0;for(n in i)i.hasOwnProperty(n)&&("object"!=typeof i[n]||Array.isArray(i[n])?e[n]=i[n]:"object"!=typeof e[n]||Array.isArray(e[n])?e[n]=t.extend({},i[n]):e[n]=t.extend(e[n],i[n]));return e}}]),t}();return n}); | ||
/*! d3-funnel - v0.7.1 | 2015 */ | ||
!function(t,e){"function"==typeof define&&define.amd?define(["d3"],e):"object"==typeof exports?module.exports=e(require("d3")):t.D3Funnel=e(t.d3)}(this,function(t){"use strict";function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var i=function(){function t(t,e){for(var i=0;i<e.length;i++){var a=e[i];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(t,a.key,a)}}return function(e,i,a){return i&&t(e.prototype,i),a&&t(e,a),e}}(),a=function(){function a(t){e(this,a),this.selector=t,this.colorizer=new n,this.labelFormatter=new l,this.navigator=new r}return i(a,null,[{key:"defaults",value:{chart:{width:350,height:400,bottomWidth:1/3,bottomPinch:0,inverted:!1,animate:!1,curve:{enabled:!1,height:20}},block:{dynamicHeight:!1,fill:{scale:t.scale.category10().domain(t.range(0,10)),type:"solid"},minHeight:!1,highlight:!1},label:{fontSize:"14px",fill:"#fff",format:"{l}: {f}"},events:{click:{block:null}}},enumerable:!0}]),i(a,[{key:"destroy",value:function(){var e=t.select(this.selector);e.selectAll("svg").remove(),e.selectAll("*").remove(),e.text("")}},{key:"draw",value:function(t){var e=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];this.destroy(),this._initialize(t,e),this._draw()}},{key:"_initialize",value:function(t,e){this._validateData(t);var i=this._getSettings(e);this.label=i.label,this.labelFormatter.setFormat(this.label.format),this.colorizer.setLabelFill(i.label.fill),this.colorizer.setScale(i.block.fill.scale),this.width=i.chart.width,this.height=i.chart.height,this.bottomWidth=i.chart.width*i.chart.bottomWidth,this.bottomPinch=i.chart.bottomPinch,this.isInverted=i.chart.inverted,this.isCurved=i.chart.curve.enabled,this.curveHeight=i.chart.curve.height,this.fillType=i.block.fill.type,this.hoverEffects=i.block.highlight,this.dynamicHeight=i.block.dynamicHeight,this.minHeight=i.block.minHeight,this.animation=i.chart.animate,this.onBlockClick=i.events.click.block,this._setBlocks(t),this.bottomLeftX=(this.width-this.bottomWidth)/2,this.dx=this._getDx(),this.dy=this._getDy()}},{key:"_validateData",value:function(t){if(Array.isArray(t)===!1)throw new Error("Data must be an array.");if(0===t.length)throw new Error("Data array must contain at least one element.");if(Array.isArray(t[0])===!1)throw new Error("Data array elements must be arrays.");if(t[0].length<2)throw new Error("Data array elements must contain a label and value.")}},{key:"_getSettings",value:function(e){var i=h.extend({},a.defaults);return i.chart.width=parseInt(t.select(this.selector).style("width"),10),i.chart.height=parseInt(t.select(this.selector).style("height"),10),i=h.extend(i,e),i.chart.width<=0&&(i.chart.width=a.defaults.chart.width),i.chart.height<=0&&(i.chart.height=a.defaults.chart.height),i}},{key:"_setBlocks",value:function(t){var e=this._getTotalCount(t);this.blocks=this._standardizeData(t,e)}},{key:"_getTotalCount",value:function(t){var e=this,i=0;return t.forEach(function(t){i+=e._getRawBlockCount(t)}),i}},{key:"_standardizeData",value:function(t,e){var i=this,a=[],n=void 0,l=void 0,r=void 0;return t.forEach(function(t,h){n=i._getRawBlockCount(t),l=n/e,r=t[0],a.push({index:h,value:n,ratio:l,height:i.height*l,formatted:i.labelFormatter.format(r,n),fill:i.colorizer.getBlockFill(t,h,i.fillType),label:{raw:r,formatted:i.labelFormatter.format(r,n),color:i.colorizer.getLabelFill(t,h)}})}),a}},{key:"_getRawBlockCount",value:function(t){return Array.isArray(t[1])?t[1][0]:t[1]}},{key:"_getDx",value:function(){return this.bottomPinch>0?this.bottomLeftX/(this.blocks.length-this.bottomPinch):this.bottomLeftX/this.blocks.length}},{key:"_getDy",value:function(){return this.isCurved?(this.height-this.curveHeight)/this.blocks.length:this.height/this.blocks.length}},{key:"_draw",value:function(){this.svg=t.select(this.selector).append("svg").attr("width",this.width).attr("height",this.height),this.blockPaths=this._makePaths(),"gradient"===this.fillType&&this._defineColorGradients(this.svg),this.isCurved&&this._drawTopOval(this.svg,this.blockPaths),this._drawBlock(0)}},{key:"_makePaths",value:function(){var t=this,e=[],i=this.dx,a=this.dy,n=0,l=this.width,r=0;this.isInverted&&(n=this.bottomLeftX,l=this.width-this.bottomLeftX);var h=0,o=0,s=0,c=this.width/2;this.isCurved&&(r=10);var u=this.height;this.minHeight!==!1&&(u=this.height-this.minHeight*this.blocks.length);var f=this.height;this.blocks.forEach(function(e,i){t.bottomPinch>0&&(t.isInverted?i<t.bottomPinch&&(f-=e.height):i>=t.blocks.length-t.bottomPinch&&(f-=e.height))});var d=2*f/(this.width-this.bottomWidth);return this.blocks.forEach(function(f,v){t.dynamicHeight&&(a=u*f.ratio,t.minHeight!==!1&&(a+=t.minHeight),t.isCurved&&(a-=t.curveHeight/t.blocks.length),h=(r+a)/d,t.isInverted&&(h=(r+a-t.height)/(-1*d)),0===t.bottomWidth&&v===t.blocks.length-1&&(h=t.width/2,t.isInverted&&(h=0)),t.bottomWidth===t.width&&(h=n),i=h-n,t.isInverted&&(i=n-h)),t.bottomPinch>0&&(t.isInverted?(t.dynamicHeight||(i=t.dx),i=v<t.bottomPinch?0:i):v>=t.blocks.length-t.bottomPinch&&(i=0)),h=n+i,o=l-i,s=r+a,t.isInverted&&(h=n-i,o=l+i),t.isCurved?e.push([[n,r,"M"],[c,r+(t.curveHeight-10),"Q"],[l,r,""],[o,s,"L"],[o,s,"M"],[c,s+t.curveHeight,"Q"],[h,s,""],[n,r,"L"]]):e.push([[n,r,"M"],[l,r,"L"],[o,s,"L"],[h,s,"L"],[n,r,"L"]]),n=h,l=o,r=s}),e}},{key:"_defineColorGradients",value:function(t){var e=t.append("defs");this.blocks.forEach(function(t,i){var a=t.fill.raw,l=n.shade(a,-.2),r=e.append("linearGradient").attr({id:"gradient-"+i}),h=[[0,l],[40,a],[60,a],[100,l]];h.forEach(function(t){r.append("stop").attr({offset:t[0]+"%",style:"stop-color:"+t[1]})})})}},{key:"_drawTopOval",value:function(t,e){var i=0,a=this.width,l=this.width/2;this.isInverted&&(i=this.bottomLeftX,a=this.width-this.bottomLeftX);var r=e[0],h=r[1][1]+this.curveHeight-10,o=this.navigator.plot([["M",i,r[0][1]],["Q",l,h],[" ",a,r[2][1]],["M",a,10],["Q",l,0],[" ",i,10]]);t.append("path").attr("fill",n.shade(this.blocks[0].fill.raw,-.4)).attr("d",o)}},{key:"_drawBlock",value:function(t){var e=this;if(t!==this.blocks.length){var i=this.svg.append("g"),a=this._getBlockPath(i,t);a.data(this._getD3Data(t)),this.animation!==!1?a.transition().duration(this.animation).ease("linear").attr("fill",this.blocks[t].fill.actual).attr("d",this._getPathDefinition(t)).each("end",function(){e._drawBlock(t+1)}):(a.attr("fill",this.blocks[t].fill.actual).attr("d",this._getPathDefinition(t)),this._drawBlock(t+1)),this.hoverEffects&&a.on("mouseover",this._onMouseOver).on("mouseout",this._onMouseOut),null!==this.onBlockClick&&a.on("click",this.onBlockClick),this._addBlockLabel(i,t)}}},{key:"_getBlockPath",value:function(t,e){var i=t.append("path");return this.animation!==!1&&this._addBeforeTransition(i,e),i}},{key:"_addBeforeTransition",value:function(t,e){var i=this.blockPaths[e],a="",n="";a=this.isCurved?this.navigator.plot([["M",i[0][0],i[0][1]],["Q",i[1][0],i[1][1]],[" ",i[2][0],i[2][1]],["L",i[2][0],i[2][1]],["M",i[2][0],i[2][1]],["Q",i[1][0],i[1][1]],[" ",i[0][0],i[0][1]]]):this.navigator.plot([["M",i[0][0],i[0][1]],["L",i[1][0],i[1][1]],["L",i[1][0],i[1][1]],["L",i[0][0],i[0][1]]]),n="solid"===this.fillType&&e>0?this.blocks[e-1].fill.actual:this.blocks[e].fill.actual,t.attr("d",a).attr("fill",n)}},{key:"_getD3Data",value:function(t){return[this.blocks[t]]}},{key:"_getPathDefinition",value:function(t){var e=[];return this.blockPaths[t].forEach(function(t){e.push([t[2],t[0],t[1]])}),this.navigator.plot(e)}},{key:"_onMouseOver",value:function(e){t.select(this).attr("fill",n.shade(e.fill.raw,-.2))}},{key:"_onMouseOut",value:function(e){t.select(this).attr("fill",e.fill.actual)}},{key:"_addBlockLabel",value:function(t,e){var i=this.blockPaths[e],a=this.blocks[e].label.formatted,n=this.blocks[e].label.color,l=this.width/2,r=this._getTextY(i);t.append("text").text(a).attr({x:l,y:r,"text-anchor":"middle","dominant-baseline":"middle",fill:n,"pointer-events":"none"}).style("font-size",this.label.fontSize)}},{key:"_getTextY",value:function(t){return this.isCurved?(t[2][1]+t[3][1])/2+this.curveHeight/this.blocks.length:(t[1][1]+t[2][1])/2}}]),a}(),n=function(){function t(){e(this,t),this.hexExpression=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i,this.labelFill=null,this.scale=null}return i(t,[{key:"setLabelFill",value:function(t){this.labelFill=t}},{key:"setScale",value:function(t){this.scale=t}},{key:"getBlockFill",value:function(t,e,i){var a=this.getBlockRawFill(t,e);return{raw:a,actual:this.getBlockActualFill(a,e,i)}}},{key:"getBlockRawFill",value:function(t,e){return t.length>2&&this.hexExpression.test(t[2])?t[2]:Array.isArray(this.scale)?this.scale[e]:this.scale(e)}},{key:"getBlockActualFill",value:function(t,e,i){return"solid"===i?t:"url(#gradient-"+e+")"}},{key:"getLabelFill",value:function(t){return t.length>3&&this.hexExpression.test(t[3])?t[3]:this.labelFill}}],[{key:"shade",value:function(e,i){var a=e.slice(1);3===a.length&&(a=t.expandHex(a));var n=parseInt(a,16),l=0>i?0:255,r=0>i?-1*i:i,h=n>>16,o=n>>8&255,s=255&n,c=16777216+65536*(Math.round((l-h)*r)+h)+256*(Math.round((l-o)*r)+o)+(Math.round((l-s)*r)+s);return"#"+c.toString(16).slice(1)}},{key:"expandHex",value:function(t){return t[0]+t[0]+t[1]+t[1]+t[2]+t[2]}}]),t}(),l=function(){function t(){e(this,t),this.expression=null}return i(t,[{key:"setFormat",value:function(t){"function"==typeof t?this.formatter=t:(this.expression=t,this.formatter=this.stringFormatter)}},{key:"format",value:function(t,e){return Array.isArray(e)?this.formatter(t,e[0],e[1]):this.formatter(t,e,null)}},{key:"stringFormatter",value:function(t,e){var i=arguments.length<=2||void 0===arguments[2]?null:arguments[2],a=i;return null===i&&(a=this.getDefaultFormattedValue(e)),this.expression.split("{l}").join(t).split("{v}").join(e).split("{f}").join(a)}},{key:"getDefaultFormattedValue",value:function(t){return t.toLocaleString()}}]),t}(),r=function(){function t(){e(this,t)}return i(t,[{key:"plot",value:function(t){var e="";return t.forEach(function(t){e+=t[0]+t[1]+","+t[2]+" "}),e.replace(/ +/g," ").trim()}}]),t}(),h=function(){function t(){e(this,t)}return i(t,null,[{key:"extend",value:function(e,i){var a=void 0;for(a in i)i.hasOwnProperty(a)&&("object"!=typeof i[a]||Array.isArray(i[a])?e[a]=i[a]:"object"!=typeof e[a]||Array.isArray(e[a])?e[a]=t.extend({},i[a]):e[a]=t.extend(e[a],i[a]));return e}}]),t}();return a}); |
@@ -0,0 +0,0 @@ The MIT License (MIT) |
{ | ||
"name": "d3-funnel", | ||
"version": "0.7.0", | ||
"version": "0.7.1", | ||
"description": "A library for rendering SVG funnel charts using D3.js", | ||
@@ -15,8 +15,8 @@ "author": "Jake Zatecky", | ||
"babel-eslint": "^4.1.3", | ||
"chai": "^3.2.0", | ||
"chai-spies": "^0.7.0", | ||
"eslint": "^1.6.0", | ||
"chai": "^3.4.0", | ||
"chai-spies": "^0.7.1", | ||
"eslint": "^1.7.3", | ||
"eslint-config-airbnb": "^0.1.0", | ||
"gulp": "^3.9.0", | ||
"gulp-babel": "^5.2.1", | ||
"gulp-babel": "^5.3.0", | ||
"gulp-concat": "^2.6.0", | ||
@@ -27,6 +27,6 @@ "gulp-eslint": "^1.0.0", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-uglify": "^1.4.1", | ||
"gulp-uglify": "^1.4.2", | ||
"gulp-wrap-umd": "^0.2.1", | ||
"lodash": "^3.10.1", | ||
"mocha": "^2.3.2" | ||
"mocha": "^2.3.3" | ||
}, | ||
@@ -33,0 +33,0 @@ "dependencies": { |
@@ -5,2 +5,4 @@ # D3 Funnel | ||
[![Build Status](https://img.shields.io/travis/jakezatecky/d3-funnel/master.svg?style=flat-square)](https://travis-ci.org/jakezatecky/d3-funnel) | ||
[![Dependency Status](https://img.shields.io/david/jakezatecky/d3-funnel.svg?style=flat-square)](https://david-dm.org/jakezatecky/d3-funnel) | ||
[![devDependency Status](https://david-dm.org/jakezatecky/d3-funnel/dev-status.svg?style=flat-square)](https://david-dm.org/jakezatecky/d3-funnel#info=devDependencies) | ||
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/jakezatecky/d3-funnel/master/LICENSE.txt) | ||
@@ -32,3 +34,3 @@ | ||
``` javascript | ||
var D3Funnel = require('d3-funnel') | ||
var D3Funnel = require('d3-funnel'); | ||
``` | ||
@@ -77,3 +79,3 @@ | ||
| `label.fill` | Any valid hex color for the label color | string | `'#fff'` | | ||
| `label.format` | Either `function(label, value)` or a format string. See below. | mixed | `'{l}: {f}'` | | ||
| `label.format` | Either `function(label, value)` or a format string. See below. | function/array | `'{l}: {f}'` | | ||
| `events.click.block` | Callback for when a block is clicked. | function | `null` | | ||
@@ -107,9 +109,11 @@ | ||
D3Funnel.defaults = _.merge(D3Funnel.defaults, { | ||
chart: { | ||
dynamicHeight: true, | ||
animate: 200, | ||
}, | ||
label: { | ||
format: '{l}: ${f}', | ||
}, | ||
block: { | ||
dynamicHeight: true, | ||
fill: { | ||
type: 'gradient', | ||
}, | ||
}, | ||
label: { | ||
format: '{l}: ${f}', | ||
}, | ||
}); | ||
@@ -133,6 +137,6 @@ ``` | ||
var data = [ | ||
['Teal', 12000, '#008080' '#080800'], | ||
['Teal', 12000, '#008080', '#080800'], | ||
['Byzantium', 4000, '#702963'], | ||
['Persimmon', 2500, '#ff634d' '#6f34fd'], | ||
['Azure', 1500, '#007fff' '#07fff0'], | ||
['Persimmon', 2500, '#ff634d', '#6f34fd'], | ||
['Azure', 1500, '#007fff', '#07fff0'], | ||
// Background ---^ ^--- Label | ||
@@ -139,0 +143,0 @@ ]; |
@@ -37,6 +37,24 @@ /* exported Colorizer */ | ||
* @param {Number} index | ||
* @param {string} type | ||
* | ||
* @return {Object} | ||
*/ | ||
getBlockFill(block, index, type) { | ||
let raw = this.getBlockRawFill(block, index); | ||
return { | ||
raw: raw, | ||
actual: this.getBlockActualFill(raw, index, type), | ||
}; | ||
} | ||
/** | ||
* Return the raw hex color for the block. | ||
* | ||
* @param {Array} block | ||
* @param {Number} index | ||
* | ||
* @return {string} | ||
*/ | ||
getBlockFill(block, index) { | ||
getBlockRawFill(block, index) { | ||
// Use the block's color, if set and valid | ||
@@ -47,2 +65,3 @@ if (block.length > 2 && this.hexExpression.test(block[2])) { | ||
// Otherwise, attempt to use the array scale | ||
if (Array.isArray(this.scale)) { | ||
@@ -52,2 +71,3 @@ return this.scale[index]; | ||
// Finally, use a functional scale | ||
return this.scale(index); | ||
@@ -57,2 +77,19 @@ } | ||
/** | ||
* Return the actual background for the block. | ||
* | ||
* @param {string} raw | ||
* @param {Number} index | ||
* @param {string} type | ||
* | ||
* @return {string} | ||
*/ | ||
getBlockActualFill(raw, index, type) { | ||
if (type === 'solid') { | ||
return raw; | ||
} | ||
return 'url(#gradient-' + index + ')'; | ||
} | ||
/** | ||
* Given a raw data block, return an appropriate label color. | ||
@@ -59,0 +96,0 @@ * |
@@ -61,4 +61,11 @@ /* global d3, Colorizer, LabelFormatter, Navigator, Utils */ | ||
destroy() { | ||
let container = d3.select(this.selector); | ||
// D3's remove method appears to be sufficient for removing the events | ||
d3.select(this.selector).selectAll('svg').remove(); | ||
container.selectAll('svg').remove(); | ||
// Remove other elements from container | ||
container.selectAll('*').remove(); | ||
// Remove inner text from container | ||
container.text(''); | ||
} | ||
@@ -142,8 +149,17 @@ | ||
_validateData(data) { | ||
if (Array.isArray(data) === false || | ||
data.length === 0 || | ||
Array.isArray(data[0]) === false || | ||
data[0].length < 2) { | ||
throw new Error('Funnel data is not valid.'); | ||
if (Array.isArray(data) === false) { | ||
throw new Error('Data must be an array.'); | ||
} | ||
if (data.length === 0) { | ||
throw new Error('Data array must contain at least one element.'); | ||
} | ||
if (Array.isArray(data[0]) === false) { | ||
throw new Error('Data array elements must be arrays.'); | ||
} | ||
if (data[0].length < 2) { | ||
throw new Error('Data array elements must contain a label and value.'); | ||
} | ||
} | ||
@@ -233,3 +249,3 @@ | ||
formatted: this.labelFormatter.format(label, count), | ||
fill: this.colorizer.getBlockFill(block, index), | ||
fill: this.colorizer.getBlockFill(block, index, this.fillType), | ||
label: { | ||
@@ -514,4 +530,4 @@ raw: label, | ||
this.blocks.forEach((block, index) => { | ||
let color = block.fill; | ||
let shade = Colorizer.shade(color, -0.25); | ||
let color = block.fill.raw; | ||
let shade = Colorizer.shade(color, -0.2); | ||
@@ -575,3 +591,3 @@ // Create linear gradient | ||
svg.append('path') | ||
.attr('fill', Colorizer.shade(this.blocks[0].fill, -0.4)) | ||
.attr('fill', Colorizer.shade(this.blocks[0].fill.raw, -0.4)) | ||
.attr('d', path); | ||
@@ -604,3 +620,3 @@ } | ||
.ease('linear') | ||
.attr('fill', this._getFillColor(index)) | ||
.attr('fill', this.blocks[index].fill.actual) | ||
.attr('d', this._getPathDefinition(index)) | ||
@@ -611,3 +627,3 @@ .each('end', () => { | ||
} else { | ||
path.attr('fill', this._getFillColor(index)) | ||
path.attr('fill', this.blocks[index].fill.actual) | ||
.attr('d', this._getPathDefinition(index)); | ||
@@ -623,3 +639,3 @@ this._drawBlock(index + 1); | ||
// ItemClick event | ||
// Add block click event | ||
if (this.onBlockClick !== null) { | ||
@@ -685,6 +701,6 @@ path.on('click', this.onBlockClick); | ||
if (this.fillType === 'solid' && index > 0) { | ||
beforeFill = this._getFillColor(index - 1); | ||
beforeFill = this.blocks[index - 1].fill.actual; | ||
// Otherwise use current background | ||
} else { | ||
beforeFill = this._getFillColor(index); | ||
beforeFill = this.blocks[index].fill.actual; | ||
} | ||
@@ -708,4 +724,2 @@ | ||
/** | ||
* Return the block fill color for the given index. | ||
* | ||
* @param {int} index | ||
@@ -715,15 +729,2 @@ * | ||
*/ | ||
_getFillColor(index) { | ||
if (this.fillType === 'solid') { | ||
return this.blocks[index].fill; | ||
} | ||
return 'url(#gradient-' + index + ')'; | ||
} | ||
/** | ||
* @param {int} index | ||
* | ||
* @return {string} | ||
*/ | ||
_getPathDefinition(index) { | ||
@@ -745,6 +746,6 @@ let commands = []; | ||
_onMouseOver(data) { | ||
d3.select(this).attr('fill', Colorizer.shade(data.fill, -0.2)); | ||
d3.select(this).attr('fill', Colorizer.shade(data.fill.raw, -0.2)); | ||
} | ||
/** | ||
/** | ||
* @param {Object} data | ||
@@ -755,3 +756,3 @@ * | ||
_onMouseOut(data) { | ||
d3.select(this).attr('fill', data.fill); | ||
d3.select(this).attr('fill', data.fill.actual); | ||
} | ||
@@ -762,3 +763,2 @@ | ||
* @param {int} index | ||
* | ||
* @return {void} | ||
@@ -765,0 +765,0 @@ */ |
@@ -45,3 +45,3 @@ /* global d3, assert, chai, D3Funnel */ | ||
it('should draw a chart on the identified target', function () { | ||
getFunnel().draw(getBasicData(), {}); | ||
getFunnel().draw(getBasicData()); | ||
@@ -57,10 +57,34 @@ assert.equal(1, getSvg()[0].length); | ||
it('should throw an exception on invalid data', function () { | ||
it('should throw an error when the data is not an array', function () { | ||
var funnel = getFunnel(); | ||
assert.throws(function () { | ||
funnel.draw(['One dimensional', 2], {}); | ||
}, Error, 'Funnel data is not valid.'); | ||
funnel.draw('Not array'); | ||
}, Error, 'Data must be an array.'); | ||
}); | ||
it('should throw an error when the data array does not have an element', function () { | ||
var funnel = getFunnel(); | ||
assert.throws(function () { | ||
funnel.draw([]); | ||
}, Error, 'Data array must contain at least one element.'); | ||
}); | ||
it('should throw an error when the first data array element is not an array', function () { | ||
var funnel = getFunnel(); | ||
assert.throws(function () { | ||
funnel.draw(['Not array']); | ||
}, Error, 'Data array elements must be arrays.'); | ||
}); | ||
it('should throw an error when the first data array element does not have two elements', function () { | ||
var funnel = getFunnel(); | ||
assert.throws(function () { | ||
funnel.draw([['Only one']]); | ||
}, Error, 'Data array elements must contain a label and value.'); | ||
}); | ||
it('should draw as many blocks as there are elements', function () { | ||
@@ -115,2 +139,34 @@ getFunnel().draw([ | ||
}); | ||
it('should remove other elements from container', function () { | ||
var container = d3.select('#funnel'), | ||
funnel = getFunnel(); | ||
// Make sure the container has no children | ||
container.selectAll('*').remove(); | ||
container.append('p'); | ||
funnel.draw(getBasicData()); | ||
var funnelChildrenSize = getSvg().selectAll('*').size(), | ||
// expect funnel children count plus funnel itself | ||
expected = funnelChildrenSize + 1, | ||
actual = container.selectAll('*').size(); | ||
assert.equal(expected, actual); | ||
}); | ||
it('should remove inner text from container', function () { | ||
var container = d3.select('#funnel'), | ||
funnel = getFunnel(); | ||
// Make sure the container has no text | ||
container.text(); | ||
container.text('to be removed'); | ||
funnel.draw(getBasicData()); | ||
// Make sure the only text in container comes from the funnel | ||
assert.equal(getSvg().text(), container.text()); | ||
}); | ||
}); | ||
@@ -122,3 +178,3 @@ | ||
funnel.draw(getBasicData(), {}); | ||
funnel.draw(getBasicData()); | ||
funnel.destroy(); | ||
@@ -135,3 +191,3 @@ | ||
getFunnel().draw(getBasicData(), {}); | ||
getFunnel().draw(getBasicData()); | ||
@@ -147,3 +203,3 @@ assert.isTrue(d3.select('#funnel text').attr('fill').indexOf('#777') > -1); | ||
getFunnel().draw(getBasicData(), {}); | ||
getFunnel().draw(getBasicData()); | ||
@@ -168,3 +224,3 @@ assert.equal(250, getSvg().node().getBBox().width); | ||
getFunnel().draw(getBasicData(), {}); | ||
getFunnel().draw(getBasicData()); | ||
@@ -366,3 +422,3 @@ assert.equal(250, getSvg().node().getBBox().height); | ||
it('should use solid fill when not set to \'gradient\'', function () { | ||
getFunnel().draw(getBasicData(), {}); | ||
getFunnel().draw(getBasicData()); | ||
@@ -369,0 +425,0 @@ // Check for valid hex string |
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
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
95900
2436
161
21