diff options
author | Jared Dillard <jdillard@netgate.com> | 2016-06-21 17:54:34 -0500 |
---|---|---|
committer | Jared Dillard <jdillard@netgate.com> | 2016-06-21 18:24:25 -0500 |
commit | 85c7840f22a9c7e2a9b0cf7b64ff51e896adf9cd (patch) | |
tree | 50f0b73d8e1daaf9c43536a24bea766ea586a955 | |
parent | 55a72d312aae1dce2f3130733a5d9ffbb6cb4691 (diff) | |
download | pfsense-85c7840f22a9c7e2a9b0cf7b64ff51e896adf9cd.zip pfsense-85c7840f22a9c7e2a9b0cf7b64ff51e896adf9cd.tar.gz |
update nvd3 files
-rw-r--r-- | src/usr/local/www/vendor/nvd3/nv.d3.css | 26 | ||||
-rw-r--r-- | src/usr/local/www/vendor/nvd3/nv.d3.js | 1671 |
2 files changed, 1141 insertions, 556 deletions
diff --git a/src/usr/local/www/vendor/nvd3/nv.d3.css b/src/usr/local/www/vendor/nvd3/nv.d3.css index 252f472..bca7ab6 100644 --- a/src/usr/local/www/vendor/nvd3/nv.d3.css +++ b/src/usr/local/www/vendor/nvd3/nv.d3.css @@ -1,4 +1,4 @@ -/* nvd3 version 1.8.2 (https://github.com/novus/nvd3) 2016-01-24 */ +/* nvd3 version 1.8.3 (https://github.com/novus/nvd3) 2016-04-26 */ .nvd3 .nv-axis { pointer-events:none; opacity: 1; @@ -164,6 +164,18 @@ } +.nv-force-node { + stroke: #fff; + stroke-width: 1.5px; +} +.nv-force-link { + stroke: #999; + stroke-opacity: .6; +} +.nv-force-node text { + stroke-width: 0px +} + .nvd3 .nv-legend .nv-disabled rect { /*fill-opacity: 0;*/ } @@ -333,6 +345,16 @@ svg.nvd3-svg { fill-opacity: .7; } +/********** +* Print +*/ + +@media print { + .nvd3 text { + stroke-width: 0; + fill-opacity: 1; + } +} .nvd3.nv-ohlcBar .nv-ticks .nv-tick { stroke-width: 1px; @@ -373,7 +395,7 @@ svg.nvd3-svg { .nvd3 .nv-parallelCoordinates .hover { fill-opacity: 1; - stroke-width: 3px; + stroke-width: 3px; } diff --git a/src/usr/local/www/vendor/nvd3/nv.d3.js b/src/usr/local/www/vendor/nvd3/nv.d3.js index 3f7b3c3..149621d 100644 --- a/src/usr/local/www/vendor/nvd3/nv.d3.js +++ b/src/usr/local/www/vendor/nvd3/nv.d3.js @@ -1,4 +1,4 @@ -/* nvd3 version 1.8.2 (https://github.com/novus/nvd3) 2016-01-24 */ +/* nvd3 version 1.8.3 (https://github.com/novus/nvd3) 2016-04-26 */ (function(){ // set up main nv object @@ -13,6 +13,11 @@ nv.charts = {}; //stores all the ready to use charts nv.logs = {}; //stores some statistics and potential error messages nv.dom = {}; //DOM manipulation functions +// Node/CommonJS - require D3 +if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') { + d3 = require('d3'); +} + nv.dispatch = d3.dispatch('render_start', 'render_end'); // Function bind polyfill @@ -157,7 +162,7 @@ if (typeof(window) !== 'undefined') { */ nv.dom.write = function(callback) { if (window.fastdom !== undefined) { - return fastdom.write(callback); + return fastdom.mutate(callback); } return callback(); }; @@ -170,10 +175,11 @@ nv.dom.write = function(callback) { */ nv.dom.read = function(callback) { if (window.fastdom !== undefined) { - return fastdom.read(callback); + return fastdom.measure(callback); } return callback(); -};/* Utility class to handle creation of an interactive layer. +}; +/* Utility class to handle creation of an interactive layer. This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch containing the X-coordinate. It can also render a vertical line where the mouse is located. @@ -258,7 +264,8 @@ nv.interactiveGuideline = function() { /* If mouseX/Y is outside of the chart's bounds, trigger a mouseOut event. */ - if (mouseX < 0 || mouseY < 0 + if (d3.event.type === 'mouseout' + || mouseX < 0 || mouseY < 0 || mouseX > availableWidth || mouseY > availableHeight || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined) || mouseOutAnyReason @@ -642,11 +649,11 @@ nv.models.tooltip = function() { var dataSeriesExists = function(d) { if (d && d.series) { - if (d.series instanceof Array) { - return !!d.series.length; + if (nv.utils.isArray(d.series)) { + return true; } // if object, it's okay just convert to array of the object - if (d.series instanceof Object) { + if (nv.utils.isObject(d.series)) { d.series = [d.series]; return true; } @@ -754,18 +761,23 @@ nv.models.tooltip = function() { // Creates new tooltip container, or uses existing one on DOM. function initTooltip() { - if (!tooltip) { + if (!tooltip || !tooltip.node()) { var container = chartContainer ? chartContainer : document.body; - // Create new tooltip div if it doesn't exist on DOM. - tooltip = d3.select(container).append("div") - .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip")) - .attr("id", id); - tooltip.style("top", 0).style("left", 0); - tooltip.style('opacity', 0); - tooltip.style('position', 'fixed'); - tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true); - tooltip.classed(nvPointerEventsClass, true); + + var data = [1]; + tooltip = d3.select(container).selectAll('.nvtooltip').data(data); + + tooltip.enter().append('div') + .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip")) + .attr("id", id) + .style("top", 0).style("left", 0) + .style('opacity', 0) + .style('position', 'fixed') + .selectAll("div, table, td, tr").classed(nvPointerEventsClass, true) + .classed(nvPointerEventsClass, true); + + tooltip.exit().remove() } } @@ -884,6 +896,25 @@ nv.utils.windowSize = function() { return (size); }; + +/* handle dumb browser quirks... isinstance breaks if you use frames +typeof returns 'object' for null, NaN is a number, etc. + */ +nv.utils.isArray = Array.isArray; +nv.utils.isObject = function(a) { + return a !== null && typeof a === 'object'; +}; +nv.utils.isFunction = function(a) { + return typeof a === 'function'; +}; +nv.utils.isDate = function(a) { + return toString.call(a) === '[object Date]'; +}; +nv.utils.isNumber = function(a) { + return !isNaN(a) && typeof a === 'number'; +}; + + /* Binds callback function to run when window is resized */ @@ -915,8 +946,7 @@ nv.utils.getColor = function(color) { return nv.utils.defaultColor(); //if passed an array, turn it into a color scale - // use isArray, instanceof fails if d3 range is created in an iframe - } else if(Array.isArray(color)) { + } else if(nv.utils.isArray(color)) { var color_scale = d3.scale.ordinal().range(color); return function(d, i) { var key = i === undefined ? d : i; @@ -956,7 +986,7 @@ nv.utils.customTheme = function(dictionary, getKey, defaultColors) { return function(series, index) { var key = getKey(series); - if (typeof dictionary[key] === 'function') { + if (nv.utils.isFunction(dictionary[key])) { return dictionary[key](); } else if (dictionary[key] !== undefined) { return dictionary[key]; @@ -1010,12 +1040,10 @@ Most common instance is when the element is in a display:none; container. Forumla is : text.length * font-size * constant_factor */ nv.utils.calcApproxTextWidth = function (svgTextElem) { - if (typeof svgTextElem.style === 'function' - && typeof svgTextElem.text === 'function') { - + if (nv.utils.isFunction(svgTextElem.style) && nv.utils.isFunction(svgTextElem.text)) { var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10); var textLength = svgTextElem.text().length; - return textLength * fontSize * 0.5; + return nv.utils.NaNtoZero(textLength * fontSize * 0.5); } return 0; }; @@ -1025,7 +1053,7 @@ nv.utils.calcApproxTextWidth = function (svgTextElem) { Numbers that are undefined, null or NaN, convert them to zeros. */ nv.utils.NaNtoZero = function(n) { - if (typeof n !== 'number' + if (!nv.utils.isNumber(n) || isNaN(n) || n === null || n === Infinity @@ -1143,9 +1171,9 @@ nv.utils.deepExtend = function(dst){ var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : []; sources.forEach(function(source) { for (var key in source) { - var isArray = dst[key] instanceof Array; - var isObject = typeof dst[key] === 'object'; - var srcObj = typeof source[key] === 'object'; + var isArray = nv.utils.isArray(dst[key]); + var isObject = nv.utils.isObject(dst[key]); + var srcObj = nv.utils.isObject(source[key]); if (isObject && !isArray && srcObj) { nv.utils.deepExtend(dst[key], source[key]); @@ -1244,7 +1272,7 @@ chart.options = nv.utils.optionsFunc.bind(chart); nv.utils.optionsFunc = function(args) { if (args) { d3.map(args).forEach((function(key,value) { - if (typeof this[key] === "function") { + if (nv.utils.isFunction(this[key])) { this[key](value); } }).bind(this)); @@ -1560,6 +1588,7 @@ nv.utils.arrayEquals = function (array1, array2) { , isOrdinal = false , ticks = null , axisLabelDistance = 0 + , fontSize = undefined , duration = 250 , dispatch = d3.dispatch('renderEnd') ; @@ -1607,6 +1636,11 @@ nv.utils.arrayEquals = function (array1, array2) { .data([axisLabelText || null]); axisLabel.exit().remove(); + //only skip when fontSize is undefined so it can be cleared with a null or blank string + if (fontSize !== undefined) { + g.selectAll('g').select("text").style('font-size', fontSize); + } + var xLabelMargin; var axisMaxMin; var w; @@ -1657,6 +1691,8 @@ nv.utils.arrayEquals = function (array1, array2) { var xTicks = g.selectAll('g').select("text"); var rotateLabelsRule = ''; if (rotateLabels%360) { + //Reset transform on ticks so textHeight can be calculated correctly + xTicks.attr('transform', ''); //Calculate the longest xTick width xTicks.each(function(d,i){ var box = this.getBoundingClientRect(); @@ -1896,6 +1932,7 @@ nv.utils.arrayEquals = function (array1, array2) { height: {get: function(){return height;}, set: function(_){height=_;}}, ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, width: {get: function(){return width;}, set: function(_){width=_;}}, + fontSize: {get: function(){return fontSize;}, set: function(_){fontSize=_;}}, // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ @@ -1929,30 +1966,36 @@ nv.models.boxPlot = function() { // Public Variables with Default Settings //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , color = nv.utils.defaultColor() - , container = null - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - , duration = 250 - , maxBoxWidth = null - ; + var margin = {top: 0, right: 0, bottom: 0, left: 0}, + width = 960, + height = 500, + id = Math.floor(Math.random() * 10000), // Create semi-unique ID in case user doesn't select one + xScale = d3.scale.ordinal(), + yScale = d3.scale.linear(), + getX = function(d) { return d.label }, // Default data model selectors. + getQ1 = function(d) { return d.values.Q1 }, + getQ2 = function(d) { return d.values.Q2 }, + getQ3 = function(d) { return d.values.Q3 }, + getWl = function(d) { return d.values.whisker_low }, + getWh = function(d) { return d.values.whisker_high }, + getColor = function(d) { return d.color }, + getOlItems = function(d) { return d.values.outliers }, + getOlValue = function(d, i, j) { return d }, + getOlLabel = function(d, i, j) { return d }, + getOlColor = function(d, i, j) { return undefined }, + color = nv.utils.defaultColor(), + container = null, + xDomain, xRange, + yDomain, yRange, + dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'), + duration = 250, + maxBoxWidth = null; //============================================================ // Private Variables //------------------------------------------------------------ - var x0, y0; + var xScale0, yScale0; var renderWatch = nv.utils.renderWatch(dispatch, duration); function chart(selection) { @@ -1965,45 +2008,38 @@ nv.models.boxPlot = function() { nv.utils.initSVG(container); // Setup Scales - x .domain(xDomain || data.map(function(d,i) { return getX(d,i); })) - .rangeBands(xRange || [0, availableWidth], .1); + xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); })) + .rangeBands(xRange || [0, availableWidth], 0.1); // if we know yDomain, no need to calculate var yData = [] if (!yDomain) { // (y-range is based on quartiles, whiskers and outliers) - - // lower values - var yMin = d3.min(data.map(function(d) { - var min_arr = []; - - min_arr.push(d.values.Q1); - if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); } - if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); } - - return d3.min(min_arr); - })); - - // upper values - var yMax = d3.max(data.map(function(d) { - var max_arr = []; - - max_arr.push(d.values.Q3); - if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); } - if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); } - - return d3.max(max_arr); - })); - + var values = [], yMin, yMax; + data.forEach(function (d, i) { + var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d); + var olItems = getOlItems(d); + if (olItems) { + olItems.forEach(function (e, i) { + values.push(getOlValue(e, i, undefined)); + }); + } + if (wl) { values.push(wl) } + if (q1) { values.push(q1) } + if (q3) { values.push(q3) } + if (wh) { values.push(wh) } + }); + yMin = d3.min(values); + yMax = d3.max(values); yData = [ yMin, yMax ] ; } - y.domain(yDomain || yData); - y.range(yRange || [availableHeight, 0]); + yScale.domain(yDomain || yData); + yScale.range(yRange || [availableHeight, 0]); //store old scales if they exist - x0 = x0 || x; - y0 = y0 || y.copy().range([y(0),y(0)]); + xScale0 = xScale0 || xScale; + yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]); // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap').data([data]); @@ -2014,15 +2050,15 @@ nv.models.boxPlot = function() { var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); boxplots .attr('class', 'nv-boxplot') - .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; }) + .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; }) .classed('hover', function(d) { return d.hover }); boxplots .watchTransition(renderWatch, 'nv-boxplot: boxplots') .style('stroke-opacity', 1) - .style('fill-opacity', .75) + .style('fill-opacity', 0.75) .delay(function(d,i) { return i * duration / data.length }) .attr('transform', function(d,i) { - return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; + return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; }); boxplots.exit().remove(); @@ -2030,97 +2066,62 @@ nv.models.boxPlot = function() { // conditionally append whisker lines boxEnter.each(function(d,i) { - var box = d3.select(this); - - ['low', 'high'].forEach(function(key) { - if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) { - box.append('line') - .style('stroke', (d.color) ? d.color : color(d,i)) - .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); - - box.append('line') - .style('stroke', (d.color) ? d.color : color(d,i)) - .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); - } - }); - }); - - // outliers - // TODO: support custom colors here - var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { - if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; } - else { return []; } - }); - outliers.enter().append('circle') - .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) }) - .on('mouseover', function(d,i,j) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - series: { key: d, color: color(d,j) }, - e: d3.event - }); - }) - .on('mouseout', function(d,i,j) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - series: { key: d, color: color(d,j) }, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); + var box = d3.select(this); + [getWl, getWh].forEach(function (f) { + if (f(d)) { + var key = (f === getWl) ? 'low' : 'high'; + box.append('line') + .style('stroke', getColor(d) || color(d,i)) + .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); + box.append('line') + .style('stroke', getColor(d) || color(d,i)) + .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); + } }); + }); - outliers.attr('class', 'nv-boxplot-outlier'); - outliers - .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') - .attr('cx', x.rangeBand() * .45) - .attr('cy', function(d,i,j) { return y(d); }) - .attr('r', '3'); - outliers.exit().remove(); - - var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); }; - var box_left = function() { return x.rangeBand() * .45 - box_width()/2; }; - var box_right = function() { return x.rangeBand() * .45 + box_width()/2; }; + var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); }; + var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; }; + var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; }; // update whisker lines and ticks - ['low', 'high'].forEach(function(key) { - var endpoint = (key === 'low') ? 'Q1' : 'Q3'; - - boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .attr('x1', x.rangeBand() * .45 ) - .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) - .attr('x2', x.rangeBand() * .45 ) - .attr('y2', function(d,i) { return y(d.values[endpoint]); }); - - boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .attr('x1', box_left ) - .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) - .attr('x2', box_right ) - .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); }); + [getWl, getWh].forEach(function (f) { + var key = (f === getWl) ? 'low' : 'high'; + var endpoint = (f === getWl) ? getQ1 : getQ3; + boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .attr('x1', xScale.rangeBand() * 0.45 ) + .attr('y1', function(d,i) { return yScale(f(d)); }) + .attr('x2', xScale.rangeBand() * 0.45 ) + .attr('y2', function(d,i) { return yScale(endpoint(d)); }); + boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .attr('x1', box_left ) + .attr('y1', function(d,i) { return yScale(f(d)); }) + .attr('x2', box_right ) + .attr('y2', function(d,i) { return yScale(f(d)); }); }); - ['low', 'high'].forEach(function(key) { - boxEnter.selectAll('.nv-boxplot-' + key) - .on('mouseover', function(d,i,j) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - series: { key: d.values['whisker_' + key], color: color(d,j) }, - e: d3.event - }); - }) - .on('mouseout', function(d,i,j) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - series: { key: d.values['whisker_' + key], color: color(d,j) }, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); - }); + [getWl, getWh].forEach(function (f) { + var key = (f === getWl) ? 'low' : 'high'; + boxEnter.selectAll('.nv-boxplot-' + key) + .on('mouseover', function(d,i,j) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + series: { key: f(d), color: getColor(d) || color(d,j) }, + e: d3.event + }); + }) + .on('mouseout', function(d,i,j) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + series: { key: f(d), color: getColor(d) || color(d,j) }, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); }); // boxes @@ -2130,12 +2131,12 @@ nv.models.boxPlot = function() { .on('mouseover', function(d,i) { d3.select(this).classed('hover', true); dispatch.elementMouseover({ - key: d.label, - value: d.label, + key: getX(d), + value: getX(d), series: [ - { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, - { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, - { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } + { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, + { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, + { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } ], data: d, index: i, @@ -2145,12 +2146,12 @@ nv.models.boxPlot = function() { .on('mouseout', function(d,i) { d3.select(this).classed('hover', false); dispatch.elementMouseout({ - key: d.label, - value: d.label, + key: getX(d), + value: getX(d), series: [ - { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, - { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, - { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } + { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, + { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, + { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } ], data: d, index: i, @@ -2164,13 +2165,12 @@ nv.models.boxPlot = function() { // box transitions boxplots.select('rect.nv-boxplot-box') .watchTransition(renderWatch, 'nv-boxplot: boxes') - .attr('y', function(d,i) { return y(d.values.Q3); }) + .attr('y', function(d,i) { return yScale(getQ3(d)); }) .attr('width', box_width) .attr('x', box_left ) - - .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 }) - .style('fill', function(d,i) { return d.color || color(d,i) }) - .style('stroke', function(d,i) { return d.color || color(d,i) }); + .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 }) + .style('fill', function(d,i) { return getColor(d) || color(d,i) }) + .style('stroke', function(d,i) { return getColor(d) || color(d,i) }); // median line boxEnter.append('line').attr('class', 'nv-boxplot-median'); @@ -2178,13 +2178,46 @@ nv.models.boxPlot = function() { boxplots.select('line.nv-boxplot-median') .watchTransition(renderWatch, 'nv-boxplot: boxplots line') .attr('x1', box_left) - .attr('y1', function(d,i) { return y(d.values.Q2); }) + .attr('y1', function(d,i) { return yScale(getQ2(d)); }) .attr('x2', box_right) - .attr('y2', function(d,i) { return y(d.values.Q2); }); + .attr('y2', function(d,i) { return yScale(getQ2(d)); }); + + // outliers + var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { + return getOlItems(d) || []; + }); + outliers.enter().append('circle') + .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) + .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) + .style('z-index', 9000) + .on('mouseover', function(d,i,j) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, + e: d3.event + }); + }) + .on('mouseout', function(d,i,j) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); + outliers.attr('class', 'nv-boxplot-outlier'); + outliers + .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') + .attr('cx', xScale.rangeBand() * 0.45) + .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); }) + .attr('r', '3'); + outliers.exit().remove(); //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); + xScale0 = xScale.copy(); + yScale0 = yScale.copy(); }); renderWatch.renderEnd('nv-boxplot immediate'); @@ -2200,20 +2233,37 @@ nv.models.boxPlot = function() { chart._options = Object.create({}, { // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}}, + q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}}, + q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}}, + wl: {get: function(){return getWl;}, set: function(_){getWl=_;}}, + wh: {get: function(){return getWh;}, set: function(_){getWh=_;}}, + itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}}, + outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}}, + outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}}, + outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}}, + outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}}, + xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}}, + yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}}, xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, id: {get: function(){return id;}, set: function(_){id=_;}}, // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, - + y: { + get: function() { + console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); + return {}; + }, + set: function(_) { + console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); + } + }, // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ margin.top = _.top !== undefined ? _.top : margin.top; @@ -2241,26 +2291,23 @@ nv.models.boxPlotChart = function() { // Public Variables with Default Settings //------------------------------------------------------------ - var boxplot = nv.models.boxPlot() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - ; + var boxplot = nv.models.boxPlot(), + xAxis = nv.models.axis(), + yAxis = nv.models.axis(); - var margin = {top: 15, right: 10, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.getColor() - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , staggerLabels = false - , tooltip = nv.models.tooltip() - , x - , y - , noData = "No Data Available." - , dispatch = d3.dispatch('beforeUpdate', 'renderEnd') - , duration = 250 - ; + var margin = {top: 15, right: 10, bottom: 50, left: 60}, + width = null, + height = null, + color = nv.utils.getColor(), + showXAxis = true, + showYAxis = true, + rightAlignYAxis = false, + staggerLabels = false, + tooltip = nv.models.tooltip(), + x, y, + noData = 'No Data Available.', + dispatch = d3.dispatch('beforeUpdate', 'renderEnd'), + duration = 250; xAxis .orient('bottom') @@ -2287,13 +2334,10 @@ nv.models.boxPlotChart = function() { if (showYAxis) renderWatch.models(yAxis); selection.each(function(data) { - var container = d3.select(this), - that = this; + var container = d3.select(this), that = this; nv.utils.initSVG(container); - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; + var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right; + var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom; chart.update = function() { dispatch.beforeUpdate(); @@ -2301,9 +2345,9 @@ nv.models.boxPlotChart = function() { }; chart.container = this; - // Display No Data message if there's nothing to show. (quartiles required at minimum) - if (!data || !data.length || - !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) { + // TODO still need to find a way to validate quartile data presence using boxPlot callbacks. + // Display No Data message if there's nothing to show. (quartiles required at minimum). + if (!data || !data.length) { var noDataText = container.selectAll('.nv-noData').data([noData]); noDataText.enter().append('text') @@ -2337,25 +2381,21 @@ nv.models.boxPlotChart = function() { .append('line'); gEnter.append('g').attr('class', 'nv-barsWrap'); - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(' + availableWidth + ',0)'); } // Main Chart Component(s) - boxplot - .width(availableWidth) - .height(availableHeight); + boxplot.width(availableWidth).height(availableHeight); var barsWrap = g.select('.nv-barsWrap') .datum(data.filter(function(d) { return !d.disabled })) barsWrap.transition().call(boxplot); - defsEnter.append('clipPath') .attr('id', 'nv-x-label-clip-' + boxplot.id()) .append('rect'); @@ -2379,7 +2419,7 @@ nv.models.boxPlotChart = function() { if (staggerLabels) { xTicks .selectAll('text') - .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 === 0 ? '5' : '17') + ')' }) } } @@ -2393,11 +2433,11 @@ nv.models.boxPlotChart = function() { } // Zero line - g.select(".nv-zeroLine line") - .attr("x1",0) - .attr("x2",availableWidth) - .attr("y1", y(0)) - .attr("y2", y(0)) + g.select('.nv-zeroLine line') + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', y(0)) + .attr('y2', y(0)) ; //============================================================ @@ -2504,8 +2544,19 @@ nv.models.bullet = function() { , tickFormat = null , color = nv.utils.getColor(['#1f77b4']) , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove') + , defaultRangeLabels = ["Maximum", "Mean", "Minimum"] + , legacyRangeClassNames = ["Max", "Avg", "Min"] ; + function sortLabels(labels, values){ + var lz = labels.slice(); + labels.sort(function(a, b){ + var iA = lz.indexOf(a); + var iB = lz.indexOf(b); + return d3.descending(values[iA], values[iB]); + }); + }; + function chart(selection) { selection.each(function(d, i) { var availableWidth = width - margin.left - margin.right, @@ -2514,13 +2565,23 @@ nv.models.bullet = function() { container = d3.select(this); nv.utils.initSVG(container); - var rangez = ranges.call(this, d, i).slice().sort(d3.descending), - markerz = markers.call(this, d, i).slice().sort(d3.descending), - measurez = measures.call(this, d, i).slice().sort(d3.descending), + var rangez = ranges.call(this, d, i).slice(), + markerz = markers.call(this, d, i).slice(), + measurez = measures.call(this, d, i).slice(), rangeLabelz = rangeLabels.call(this, d, i).slice(), markerLabelz = markerLabels.call(this, d, i).slice(), measureLabelz = measureLabels.call(this, d, i).slice(); + // Sort labels according to their sorted values + sortLabels(rangeLabelz, rangez); + sortLabels(markerLabelz, markerz); + sortLabels(measureLabelz, measurez); + + // sort values descending + rangez.sort(d3.descending); + markerz.sort(d3.descending); + measurez.sort(d3.descending); + // Setup Scales // Compute the new x-scale. var x1 = d3.scale.linear() @@ -2545,9 +2606,14 @@ nv.models.bullet = function() { var gEnter = wrapEnter.append('g'); var g = wrap.select('g'); - gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); - gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); - gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); + for(var i=0,il=rangez.length; i<il; i++){ + var rangeClassNames = 'nv-range nv-range'+i; + if(i <= 2){ + rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i]; + } + gEnter.append('rect').attr('class', rangeClassNames); + } + gEnter.append('rect').attr('class', 'nv-measure'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); @@ -2557,25 +2623,14 @@ nv.models.bullet = function() { var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; - g.select('rect.nv-rangeMax') - .attr('height', availableHeight) - .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) - .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) - .datum(rangeMax > 0 ? rangeMax : rangeMin) - - g.select('rect.nv-rangeAvg') - .attr('height', availableHeight) - .attr('width', w1(rangeAvg)) - .attr('x', xp1(rangeAvg)) - .datum(rangeAvg) - - g.select('rect.nv-rangeMin') - .attr('height', availableHeight) - .attr('width', w1(rangeMax)) - .attr('x', xp1(rangeMax)) - .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) - .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) - .datum(rangeMax > 0 ? rangeMin : rangeMax) + for(var i=0,il=rangez.length; i<il; i++){ + var range = rangez[i]; + g.select('rect.nv-range'+i) + .attr('height', availableHeight) + .attr('width', w1(range)) + .attr('x', xp1(range)) + .datum(range) + } g.select('rect.nv-measure') .style('fill', color) @@ -2649,7 +2704,7 @@ nv.models.bullet = function() { wrap.selectAll('.nv-range') .on('mouseover', function(d,i) { - var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); + var label = rangeLabelz[i] || defaultRangeLabels[i]; dispatch.elementMouseover({ value: d, label: label, @@ -2664,7 +2719,7 @@ nv.models.bullet = function() { }) }) .on('mouseout', function(d,i) { - var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); + var label = rangeLabelz[i] || defaultRangeLabels[i]; dispatch.elementMouseout({ value: d, label: label, @@ -3365,7 +3420,9 @@ nv.models.cumulativeLineChart = function() { gEnter.append('g').attr('class', 'nv-controlsWrap'); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth); g.select('.nv-legendWrap') @@ -3382,7 +3439,9 @@ nv.models.cumulativeLineChart = function() { } // Controls - if (showControls) { + if (!showControls) { + g.select('.nv-controlsWrap').selectAll('*').remove(); + } else { var controlsData = [ { key: 'Re-scale y-axis', disabled: !rescaleY } ]; @@ -4158,8 +4217,11 @@ nv.models.discreteBarChart = function() { gEnter.append('g').attr('class', 'nv-legendWrap'); g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (showLegend) { + + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth); g.select('.nv-legendWrap') @@ -4180,11 +4242,6 @@ nv.models.discreteBarChart = function() { .attr("transform", "translate(" + availableWidth + ",0)"); } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - // Main Chart Component(s) discretebar .width(availableWidth) @@ -4494,6 +4551,195 @@ nv.models.distribution = function() { return chart; } +nv.models.forceDirectedGraph = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , container = null + , dispatch = d3.dispatch('renderEnd') + , color = nv.utils.getColor(['#000']) + , tooltip = nv.models.tooltip() + , noData = null + // Force directed graph specific parameters [default values] + , linkStrength = 0.1 + , friction = 0.9 + , linkDist = 30 + , charge = -120 + , gravity = 0.1 + , theta = 0.8 + , alpha = 0.1 + , radius = 5 + // These functions allow to add extra attributes to ndes and links + ,nodeExtras = function(nodes) { /* Do nothing */ } + ,linkExtras = function(links) { /* Do nothing */ } + ; + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + + function chart(selection) { + renderWatch.reset(); + + selection.each(function(data) { + container = d3.select(this); + nv.utils.initSVG(container); + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + container + .attr("width", availableWidth) + .attr("height", availableHeight); + + // Display No Data message if there's nothing to show. + if (!data || !data.links || !data.nodes) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + container.selectAll('*').remove(); + + // Collect names of all fields in the nodes + var nodeFieldSet = new Set(); + data.nodes.forEach(function(node) { + var keys = Object.keys(node); + keys.forEach(function(key) { + nodeFieldSet.add(key); + }); + }); + + var force = d3.layout.force() + .nodes(data.nodes) + .links(data.links) + .size([availableWidth, availableHeight]) + .linkStrength(linkStrength) + .friction(friction) + .linkDistance(linkDist) + .charge(charge) + .gravity(gravity) + .theta(theta) + .alpha(alpha) + .start(); + + var link = container.selectAll(".link") + .data(data.links) + .enter().append("line") + .attr("class", "nv-force-link") + .style("stroke-width", function(d) { return Math.sqrt(d.value); }); + + var node = container.selectAll(".node") + .data(data.nodes) + .enter() + .append("g") + .attr("class", "nv-force-node") + .call(force.drag); + + node + .append("circle") + .attr("r", radius) + .style("fill", function(d) { return color(d) } ) + .on("mouseover", function(evt) { + container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) + .attr('y1', evt.py); + container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) + .attr('x2', evt.px); + + // Add 'series' object to + var nodeColor = color(evt); + evt.series = []; + nodeFieldSet.forEach(function(field) { + evt.series.push({ + color: nodeColor, + key: field, + value: evt[field] + }); + }); + tooltip.data(evt).hidden(false); + }) + .on("mouseout", function(d) { + tooltip.hidden(true); + }); + + tooltip.headerFormatter(function(d) {return "Node";}); + + // Apply extra attributes to nodes and links (if any) + linkExtras(link); + nodeExtras(node); + + force.on("tick", function() { + link.attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + + node.attr("transform", function(d) { + return "translate(" + d.x + ", " + d.y + ")"; + }); + }); + }); + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + + // Force directed graph specific parameters + linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}}, + friction: {get: function(){return friction;}, set: function(_){friction=_;}}, + linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}}, + charge: {get: function(){return charge;}, set: function(_){charge=_;}}, + gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, + theta: {get: function(){return theta;}, set: function(_){theta=_;}}, + alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}}, + radius: {get: function(){return radius;}, set: function(_){radius=_;}}, + + //functor options + x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + nodeExtras: {get: function(){return nodeExtras;}, set: function(_){ + nodeExtras = _; + }}, + linkExtras: {get: function(){return linkExtras;}, set: function(_){ + linkExtras = _; + }} + }); + + chart.dispatch = dispatch; + chart.tooltip = tooltip; + nv.utils.initOptions(chart); + return chart; +}; nv.models.furiousLegend = function() { "use strict"; @@ -4675,13 +4921,13 @@ nv.models.furiousLegend = function() { var seriesWidths = []; series.each(function(d,i) { var legendText; - if (getKey(d).length > maxKeyLength) { + if (getKey(d) && (getKey(d).length > maxKeyLength)) { var trimmedKey = getKey(d).substring(0, maxKeyLength); legendText = d3.select(this).select('text').text(trimmedKey + "..."); d3.select(this).append("svg:title").text(getKey(d)); } else { legendText = d3.select(this).select('text'); - } + } var nodeTextLength; try { nodeTextLength = legendText.node().getComputedTextLength(); @@ -5180,7 +5426,9 @@ nv.models.historicalBarChart = function(bar_model) { gEnter.append('g').attr('class', 'nv-interactive'); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth); g.select('.nv-legendWrap') @@ -5534,7 +5782,7 @@ nv.models.legend = function() { .attr('class','nv-legend-symbol') .attr('r', 5); - seriesShape = series.select('circle'); + seriesShape = series.select('.nv-legend-symbol'); } else if (vers == 'furious') { seriesEnter.append('rect') .style('stroke-width', 2) @@ -5651,13 +5899,13 @@ nv.models.legend = function() { var seriesWidths = []; series.each(function(d,i) { var legendText; - if (getKey(d).length > maxKeyLength) { + if (getKey(d) && (getKey(d).length > maxKeyLength)) { var trimmedKey = getKey(d).substring(0, maxKeyLength); legendText = d3.select(this).select('text').text(trimmedKey + "..."); d3.select(this).append("svg:title").text(getKey(d)); } else { legendText = d3.select(this).select('text'); - } + } var nodeTextLength; try { nodeTextLength = legendText.node().getComputedTextLength(); @@ -6104,6 +6352,7 @@ nv.models.lineChart = function() { , width = null , height = null , showLegend = true + , legendPosition = 'top' , showXAxis = true , showYAxis = true , rightAlignYAxis = false @@ -6254,20 +6503,27 @@ nv.models.lineChart = function() { contextEnter.append('g').attr('class', 'nv-x nv-brush'); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth); g.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0); - } + if (legendPosition === 'bottom') { + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (availableHeight1) +')'); + } else if (legendPosition === 'top') { + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0); + } - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')'); + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } } wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); @@ -6491,11 +6747,13 @@ nv.models.lineChart = function() { allData[indexToHighlight].highlight = true; } + var defaultValueFormatter = function(d,i) { + return d == null ? "N/A" : yAxis.tickFormat()(d); + }; + interactiveLayer.tooltip .chartContainer(chart.container.parentNode) - .valueFormatter(function(d,i) { - return d === null ? "N/A" : yAxis.tickFormat()(d); - }) + .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) .data({ value: chart.x()( singlePoint,pointIndex ), index: pointIndex, @@ -6669,6 +6927,7 @@ nv.models.lineChart = function() { width: {get: function(){return width;}, set: function(_){width=_;}}, height: {get: function(){return height;}, set: function(_){height=_;}}, showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, @@ -6749,8 +7008,7 @@ nv.models.lineWithFocusChart = function() { return nv.models.lineChart() .margin({ bottom: 30 }) .focusEnable( true ); -}; -nv.models.linePlusBarChart = function() { +};nv.models.linePlusBarChart = function() { "use strict"; //============================================================ @@ -6822,15 +7080,15 @@ nv.models.linePlusBarChart = function() { //------------------------------------------------------------ var getBarsAxis = function() { - return switchYAxisOrder - ? { main: y1Axis, focus: y3Axis } - : { main: y2Axis, focus: y4Axis } + return !switchYAxisOrder + ? { main: y2Axis, focus: y4Axis } + : { main: y1Axis, focus: y3Axis } } var getLinesAxis = function() { - return switchYAxisOrder - ? { main: y2Axis, focus: y4Axis } - : { main: y1Axis, focus: y3Axis } + return !switchYAxisOrder + ? { main: y1Axis, focus: y3Axis } + : { main: y2Axis, focus: y4Axis } } var stateGetter = function(data) { @@ -6900,7 +7158,12 @@ nv.models.linePlusBarChart = function() { var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 - x = bars.xScale(); + if (dataBars.length && !switchYAxisOrder) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } + x2 = x2Axis.scale(); // select the scales and series based on the position of the yAxis @@ -6959,7 +7222,9 @@ nv.models.linePlusBarChart = function() { // Legend //------------------------------------------------------------ - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; var legendXPosition = legend.align() ? legendWidth : 0; @@ -7234,8 +7499,14 @@ nv.models.linePlusBarChart = function() { .tickSize(-availableWidth, 0); y2Axis .scale(y2) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) - .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ); + + // Show the y2 rules only if y1 has none + if(!switchYAxisOrder) { + y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0); + } else { + y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0); + } // Calculate opacity of the axis var barsOpacity = dataBars.length ? 1 : 0; @@ -7379,11 +7650,20 @@ nv.models.linePlusBarChart = function() { switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){ // Switch the tick format for the yAxis if(switchYAxisOrder !== _) { - var tickFormat = y1Axis.tickFormat(); - y1Axis.tickFormat(y2Axis.tickFormat()); - y2Axis.tickFormat(tickFormat); + var y1 = y1Axis; + y1Axis = y2Axis; + y2Axis = y1; + + var y3 = y3Axis; + y3Axis = y4Axis; + y4Axis = y3; } switchYAxisOrder=_; + + y1Axis.orient('left'); + y2Axis.orient('right'); + y3Axis.orient('left'); + y4Axis.orient('right'); }} }); @@ -7495,7 +7775,7 @@ nv.models.multiBar = function() { }); // HACK for negative value stacking - if (stacked) { + if (stacked && data.length > 0) { data[0].values.map(function(d,i) { var posBase = 0, negBase = 0; data.map(function(d, idx) { @@ -7819,7 +8099,8 @@ nv.models.multiBar = function() { nv.utils.initOptions(chart); return chart; -};nv.models.multiBarChart = function() { +}; +nv.models.multiBarChart = function() { "use strict"; //============================================================ @@ -7979,7 +8260,9 @@ nv.models.multiBar = function() { gEnter.append('g').attr('class', 'nv-interactive'); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth - controlWidth()); g.select('.nv-legendWrap') @@ -7996,7 +8279,9 @@ nv.models.multiBar = function() { } // Controls - if (showControls) { + if (!showControls) { + g.select('.nv-controlsWrap').selectAll('*').remove(); + } else { var controlsData = [ { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } @@ -8480,10 +8765,13 @@ nv.models.multiBarHorizontal = function() { }); }) .on('click', function(d,i) { + var element = this; dispatch.elementClick({ data: d, index: i, - color: d3.select(this).style("fill") + color: d3.select(this).style("fill"), + event: d3.event, + element: element }); d3.event.stopPropagation(); }) @@ -8807,7 +9095,9 @@ nv.models.multiBarHorizontalChart = function() { gEnter.append('g').attr('class', 'nv-controlsWrap'); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth - controlWidth()); g.select('.nv-legendWrap') @@ -8824,7 +9114,9 @@ nv.models.multiBarHorizontalChart = function() { } // Controls - if (showControls) { + if (!showControls) { + g.select('.nv-controlsWrap').selectAll('*').remove(); + } else { var controlsData = [ { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } @@ -9044,7 +9336,7 @@ nv.models.multiChart = function() { yDomain2, getX = function(d) { return d.x }, getY = function(d) { return d.y}, - interpolate = 'monotone', + interpolate = 'linear', useVoronoi = true, interactiveLayer = nv.interactiveGuideline(), useInteractiveGuideline = false, @@ -9124,7 +9416,7 @@ nv.models.multiChart = function() { }) }); - x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return getX(d) })) + x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x })) .range([0, availableWidth]); var wrap = container.selectAll('g.wrap.multiChart').data([data]); @@ -9150,7 +9442,10 @@ nv.models.multiChart = function() { return data[i].color || color(d, i); }); - if (showLegend) { + // Legend + if (!showLegend) { + g.select('.legendWrap').selectAll('*').remove(); + } else { var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; var legendXPosition = legend.align() ? legendWidth : 0; @@ -9203,10 +9498,12 @@ nv.models.multiChart = function() { stack1 .width(availableWidth) .height(availableHeight) + .interpolate(interpolate) .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); stack2 .width(availableWidth) .height(availableHeight) + .interpolate(interpolate) .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); @@ -9323,6 +9620,9 @@ nv.models.multiChart = function() { }; tooltip .duration(0) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) .valueFormatter(function(d, i) { return yaxis.tickFormat()(d, i); }) @@ -9340,6 +9640,9 @@ nv.models.multiChart = function() { }; tooltip .duration(100) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) .valueFormatter(function(d, i) { return yaxis.tickFormat()(d, i); }) @@ -9353,6 +9656,9 @@ nv.models.multiChart = function() { evt.point['y'] = stack1.y()(evt.point); tooltip .duration(0) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) .valueFormatter(function(d, i) { return yaxis.tickFormat()(d, i); }) @@ -9371,6 +9677,9 @@ nv.models.multiChart = function() { }; tooltip .duration(0) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) .valueFormatter(function(d, i) { return yaxis.tickFormat()(d, i); }) @@ -9433,6 +9742,9 @@ nv.models.multiChart = function() { interactiveLayer.tooltip .chartContainer(chart.container.parentNode) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) .valueFormatter(function(d,i) { var yAxis = allData[i].yAxis; return d === null ? "N/A" : yAxis.tickFormat()(d); @@ -9835,8 +10147,11 @@ nv.models.parallelCoordinates = function() { var margin = {top: 30, right: 0, bottom: 10, left: 0} , width = null , height = null + , availableWidth = null + , availableHeight = null , x = d3.scale.ordinal() , y = {} + , undefinedValuesLabel = "undefined values" , dimensionData = [] , enabledDimensions = [] , dimensionNames = [] @@ -9859,19 +10174,17 @@ nv.models.parallelCoordinates = function() { // Private Variables //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); function chart(selection) { renderWatch.reset(); selection.each(function(data) { var container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + availableWidth = nv.utils.availableWidth(width, container, margin); + availableHeight = nv.utils.availableHeight(height, container, margin); nv.utils.initSVG(container); - //Convert old data to new format (name, values) if (data[0].values === undefined) { var newData = []; @@ -9892,7 +10205,6 @@ nv.models.parallelCoordinates = function() { dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key }); enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; }); - // Setup Scales x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; })); @@ -9900,7 +10212,8 @@ nv.models.parallelCoordinates = function() { // Extract the list of dimensions and create a scale for each. var oldDomainMaxValue = {}; var displayMissingValuesline = false; - + var currentTicks = []; + dimensionNames.forEach(function(d) { var extent = d3.extent(dataValues, function (p) { return +p[d]; }); var min = extent[0]; @@ -9943,7 +10256,6 @@ nv.models.parallelCoordinates = function() { .range([(availableHeight - 12) * 0.9, 0]); axisWithUndefinedValues = []; - y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend); }); @@ -9980,8 +10292,8 @@ nv.models.parallelCoordinates = function() { .attr("y2", function(d) { return d[3]; }); //Add the text "undefined values" under the missing value line - missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]); - missingValueslineText.append('text').data(["undefined values"]); + missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]); + missingValueslineText.append('text').data([undefinedValuesLabel]); missingValueslineText.enter().append('text'); missingValueslineText.exit().remove(); missingValueslineText.attr("y", availableHeight) @@ -10007,7 +10319,9 @@ nv.models.parallelCoordinates = function() { d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1); dispatch.elementMouseover({ label: d.name, - color: d.color || color(d, i) + color: d.color || color(d, i), + values: d.values, + dimensions: enabledDimensions }); }); @@ -10035,13 +10349,14 @@ nv.models.parallelCoordinates = function() { // Add an axis and title. dimensionsEnter.append('text') - .attr('class', 'nv-label') + .attr('class', 'nv-label') .style("cursor", "move") .attr('dy', '-1em') .attr('text-anchor', 'middle') .on("mouseover", function(d, i) { dispatch.elementMouseover({ - label: d.tooltip || d.key + label: d.tooltip || d.key, + color: d.color }); }) .on("mouseout", function(d, i) { @@ -10057,10 +10372,6 @@ nv.models.parallelCoordinates = function() { dimensionsEnter.append('g').attr('class', 'nv-brushBackground'); dimensions.exit().remove(); dimensions.select('.nv-label').text(function (d) { return d.key }); - dimensions.select('.nv-axis') - .each(function (d, i) { - d3.select(this).call(axis.scale(y[d.key]).tickFormat(d3.format(d.format))); - }); // Add and store a brush for each axis. restoreBrush(displayBrush); @@ -10138,27 +10449,30 @@ nv.models.parallelCoordinates = function() { }); dimensions.select('.nv-brushBackground') - .each(function (d) { - d3.select(this).call(y[d.key].brush); + .each(function (d) { + d3.select(this).call(y[d.key].brush); - }) - .selectAll('rect') - .attr('x', -8) - .attr('width', 16); + }) + .selectAll('rect') + .attr('x', -8) + .attr('width', 16); + + updateTicks(); } // Handles a brush event, toggling the display of foreground lines. function brushstart() { //If brush aren't visible, show it before brushing again. if (displayBrush === false) { + displayBrush = true; restoreBrush(true); } } // Handles a brush event, toggling the display of foreground lines. function brush() { - actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }), - extents = actives.map(function(p) { return y[p].brush.extent(); }); + actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }); + extents = actives.map(function(p) { return y[p].brush.extent(); }); filters = []; //erase current filters actives.forEach(function(d,i) { @@ -10179,7 +10493,9 @@ nv.models.parallelCoordinates = function() { if (isActive) active.push(d); return isActive ? null : 'none'; }); - + + updateTicks(); + dispatch.brush({ filters: filters, active: active @@ -10194,13 +10510,30 @@ nv.models.parallelCoordinates = function() { f.hasOnlyNaN = true; }); dispatch.brushEnd(active, hasActiveBrush); + } + function updateTicks() { + dimensions.select('.nv-axis') + .each(function (d, i) { + var f = filters.filter(function (k) { return k.dimension == d.key; }); + currentTicks[d.key] = y[d.key].domain(); + + //If brush are available, display brush extent + if (f.length != 0 && displayBrush) + { + currentTicks[d.key] = []; + if (f[0].extent[1] > y[d.key].domain()[0]) + currentTicks[d.key] = [f[0].extent[1]]; + if (f[0].extent[0] >= y[d.key].domain()[0]) + currentTicks[d.key].push(f[0].extent[0]); + } + + d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key])); + }); } function dragStart(d) { dragging[d.key] = this.parentNode.__origin__ = x(d.key); background.attr("visibility", "hidden"); - } - function dragMove(d) { dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x)); foreground.attr("d", path); @@ -10209,7 +10542,6 @@ nv.models.parallelCoordinates = function() { x.domain(enabledDimensions.map(function (d) { return d.key; })); dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; }); } - function dragEnd(d, i) { delete this.parentNode.__origin__; delete dragging[d.key]; @@ -10222,22 +10554,11 @@ nv.models.parallelCoordinates = function() { dispatch.dimensionsOrder(enabledDimensions); } - function resetBrush() { - filters = []; - active = []; - dispatch.stateChange(); - } - function resetDrag() { - dimensionName.map(function (d, i) { return d.currentPosition = d.originalPosition; }); - dispatch.stateChange(); - } - function dimensionPosition(d) { var v = dragging[d]; return v == null ? x(d) : v; } }); - return chart; } @@ -10257,7 +10578,8 @@ nv.models.parallelCoordinates = function() { filters: { get: function () { return filters; }, set: function (_) { filters = _; } }, active: { get: function () { return active; }, set: function (_) { active = _; } }, lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}}, - + undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}}, + // deprecated options dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) { // deprecated after 1.8.1 @@ -10267,8 +10589,7 @@ nv.models.parallelCoordinates = function() { } else { _.forEach(function (k, i) { dimensionData[i].key= k }) } - } - }, + }}, dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) { // deprecated after 1.8.1 nv.deprecated('dimensionNames', 'use dimensionData instead'); @@ -10290,7 +10611,6 @@ nv.models.parallelCoordinates = function() { } }}, - // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ margin.top = _.top !== undefined ? _.top : margin.top; @@ -10302,7 +10622,6 @@ nv.models.parallelCoordinates = function() { color = nv.utils.getColor(_); }} }); - nv.utils.initOptions(chart); return chart; }; @@ -10324,10 +10643,10 @@ nv.models.parallelCoordinatesChart = function () { , color = nv.utils.defaultColor() , state = nv.utils.state() , dimensionData = [] - , dimensionNames = [] , displayBrush = true , defaultState = null , noData = null + , nanValue = "undefined" , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd') , controlWidth = function () { return showControls ? 180 : 0 } ; @@ -10358,6 +10677,20 @@ nv.models.parallelCoordinatesChart = function () { } }; + tooltip.contentGenerator(function(data) { + var str = '<table><thead><tr><td class="legend-color-guide"><div style="background-color:' + data.color + '"></div></td><td><strong>' + data.key + '</strong></td></tr></thead>'; + if(data.series.length !== 0) + { + str = str + '<tbody><tr><td height ="10px"></td></tr>'; + data.series.forEach(function(d){ + str = str + '<tr><td class="legend-color-guide"><div style="background-color:' + d.color + '"></div></td><td class="key">' + d.key + '</td><td class="value">' + d.value + '</td></tr>'; + }); + str = str + '</tbody>'; + } + str = str + '</table>'; + return str; + }); + //============================================================ // Chart function //------------------------------------------------------------ @@ -10392,21 +10725,6 @@ nv.models.parallelCoordinatesChart = function () { d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition; }); - var currentDimensions = dimensionNames.map(function (d) { return d.key; }); - var newDimensions = dimensionData.map(function (d) { return d.key; }); - dimensionData.forEach(function (k, i) { - var idx = currentDimensions.indexOf(k.key); - if (idx < 0) { - dimensionNames.splice(i, 0, k); - } else { - var gap = dimensionNames[idx].currentPosition - dimensionNames[idx].originalPosition; - dimensionNames[idx].originalPosition = k.originalPosition; - dimensionNames[idx].currentPosition = k.originalPosition + gap; - } - }); - //Remove old dimensions - dimensionNames = dimensionNames.filter(function (d) { return newDimensions.indexOf(d.key) >= 0; }); - if (!defaultState) { var key; defaultState = {}; @@ -10442,12 +10760,14 @@ nv.models.parallelCoordinatesChart = function () { .attr("height", (availableHeight > 0) ? availableHeight : 0); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { legend.width(availableWidth) .color(function (d) { return "rgb(188,190,192)"; }); g.select('.nv-legendWrap') - .datum(dimensionNames.sort(function (a, b) { return a.originalPosition - b.originalPosition; })) + .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; })) .call(legend); if (margin.top != legend.height()) { @@ -10459,14 +10779,11 @@ nv.models.parallelCoordinatesChart = function () { } wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - - // Main Chart Component(s) parallelCoordinates .width(availableWidth) .height(availableHeight) - .dimensionData(dimensionNames) + .dimensionData(dimensionData) .displayBrush(displayBrush); var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ') @@ -10498,21 +10815,21 @@ nv.models.parallelCoordinatesChart = function () { //Update dimensions order and display reset sorting button parallelCoordinates.dispatch.on('dimensionsOrder', function (e) { - dimensionNames.sort(function (a, b) { return a.currentPosition - b.currentPosition; }); + dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }); var isSorted = false; - dimensionNames.forEach(function (d, i) { + dimensionData.forEach(function (d, i) { d.currentPosition = i; if (d.currentPosition !== d.originalPosition) isSorted = true; }); - dispatch.dimensionsOrder(dimensionNames, isSorted); + dispatch.dimensionsOrder(dimensionData, isSorted); }); // Update chart from a state object passed to event handler dispatch.on('changeState', function (e) { if (typeof e.disabled !== 'undefined') { - dimensionNames.forEach(function (series, i) { + dimensionData.forEach(function (series, i) { series.disabled = e.disabled[i]; }); state.disabled = e.disabled; @@ -10530,11 +10847,27 @@ nv.models.parallelCoordinatesChart = function () { //------------------------------------------------------------ parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) { - evt['series'] = { + var tp = { key: evt.label, - color: evt.color - }; - tooltip.data(evt).hidden(false); + color: evt.color, + series: [] + } + if(evt.values){ + Object.keys(evt.values).forEach(function (d) { + var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0]; + if(dim){ + var v; + if (isNaN(evt.values[d]) || isNaN(parseFloat(evt.values[d]))) { + v = nanValue; + } else { + v = dim.format(evt.values[d]); + } + tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color }); + } + }); + tp.series.sort(function(a,b) {return a.idx - b.idx}); + } + tooltip.data(tp).hidden(false); }); parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) { @@ -10553,7 +10886,6 @@ nv.models.parallelCoordinatesChart = function () { chart.parallelCoordinates = parallelCoordinates; chart.legend = legend; chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); chart._options = Object.create({}, { @@ -10565,7 +10897,8 @@ nv.models.parallelCoordinatesChart = function () { dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } }, displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } }, noData: { get: function () { return noData; }, set: function (_) { noData = _; } }, - + nanValue: { get: function () { return nanValue; }, set: function (_) { nanValue = _; } }, + // options that require extra logic in the setter margin: { get: function () { return margin; }, @@ -10587,7 +10920,8 @@ nv.models.parallelCoordinatesChart = function () { nv.utils.initOptions(chart); return chart; - };nv.models.pie = function() { + }; +nv.models.pie = function() { "use strict"; //============================================================ @@ -10649,9 +10983,15 @@ nv.models.parallelCoordinatesChart = function () { arcsRadiusInner.push(inner); } } else { - arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; }); - arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; }); - donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); })); + if(growOnHover){ + arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; }); + arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; }); + donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); })); + } else { + arcsRadiusOuter = arcsRadius.map(function (d) { return d.outer * radius; }); + arcsRadiusInner = arcsRadius.map(function (d) { return d.inner * radius; }); + donutRatio = d3.min(arcsRadius.map(function (d) { return d.inner; })); + } } nv.utils.initSVG(container); @@ -11119,7 +11459,9 @@ nv.models.pieChart = function() { gEnter.append('g').attr('class', 'nv-legendWrap'); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { if (legendPosition === "top") { legend.width( availableWidth ).key(pie.x()); @@ -11219,6 +11561,8 @@ nv.models.pieChart = function() { // use Object get/set functionality to map between vars and chart functions chart._options = Object.create({}, { // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, noData: {get: function(){return noData;}, set: function(_){noData=_;}}, showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, @@ -11288,6 +11632,7 @@ nv.models.scatter = function() { , useVoronoi = true , duration = 250 , interactiveUpdateDelay = 300 + , showLabels = false ; @@ -11300,8 +11645,34 @@ nv.models.scatter = function() { , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips , renderWatch = nv.utils.renderWatch(dispatch, duration) , _sizeRange_def = [16, 256] + , _caches ; + function getCache(d) { + var cache, i; + cache = _caches = _caches || {}; + i = d[0].series; + cache = cache[i] = cache[i] || {}; + i = d[1]; + cache = cache[i] = cache[i] || {}; + return cache; + } + + function getDiffs(d) { + var i, key, + point = d[0], + cache = getCache(d), + diffs = false; + for (i = 1; i < arguments.length; i ++) { + key = arguments[i]; + if (cache[key] !== point[key] || !cache.hasOwnProperty(key)) { + cache[key] = point[key]; + diffs = true; + } + } + return diffs; + } + function chart(selection) { renderWatch.reset(); selection.each(function(data) { @@ -11319,6 +11690,7 @@ nv.models.scatter = function() { }); // Setup Scales + var logScale = chart.yScale().name === d3.scale.log().name ? true : false; // remap and flatten the data for use in calculating the scales' domains var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance d3.merge( @@ -11337,7 +11709,7 @@ nv.models.scatter = function() { else x.range(xRange || [0, availableWidth]); - if (chart.yScale().name === "o") { + if (logScale) { var min = d3.min(seriesData.map(function(d) { if (d.y !== 0) return d.y; })); y.clamp(true) .domain(yDomain || d3.extent(seriesData.map(function(d) { @@ -11378,6 +11750,8 @@ nv.models.scatter = function() { y0 = y0 || y; z0 = z0 || z; + var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1); + // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id); @@ -11421,8 +11795,8 @@ nv.models.scatter = function() { var pX = getX(point,pointIndex); var pY = getY(point,pointIndex); - return [x(pX)+ Math.random() * 1e-4, - y(pY)+ Math.random() * 1e-4, + return [nv.utils.NaNtoZero(x(pX))+ Math.random() * 1e-4, + nv.utils.NaNtoZero(y(pY))+ Math.random() * 1e-4, groupIndex, pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates }) @@ -11621,6 +11995,7 @@ nv.models.scatter = function() { .attr('class', function(d,i) { return (d.classed || '') + ' nv-group nv-series-' + i; }) + .classed('nv-noninteractive', !interactive) .classed('hover', function(d) { return d.hover }); groups.watchTransition(renderWatch, 'scatter: groups') .style('fill', function(d,i) { return color(d, i) }) @@ -11640,6 +12015,9 @@ nv.models.scatter = function() { }) }); points.enter().append('path') + .attr('class', function (d) { + return 'nv-point nv-point-' + d[1]; + }) .style('fill', function (d) { return d.color }) .style('stroke', function (d) { return d.color }) .attr('transform', function(d) { @@ -11657,25 +12035,66 @@ nv.models.scatter = function() { return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')' }) .remove(); - points.each(function(d) { - d3.select(this) - .classed('nv-point', true) - .classed('nv-point-' + d[1], true) - .classed('nv-noninteractive', !interactive) - .classed('hover',false) - ; - }); - points + points.filter(function (d) { return scaleDiff || getDiffs(d, 'x', 'y'); }) .watchTransition(renderWatch, 'scatter points') .attr('transform', function(d) { //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1]))); return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')' - }) + }); + points.filter(function (d) { return scaleDiff || getDiffs(d, 'shape', 'size'); }) + .watchTransition(renderWatch, 'scatter points') .attr('d', nv.utils.symbol() .type(function(d) { return getShape(d[0]); }) .size(function(d) { return z(getSize(d[0],d[1])) }) ); + + // add label a label to scatter chart + if(showLabels) + { + var titles = groups.selectAll('.nv-label') + .data(function(d) { + return d.values.map( + function (point, pointIndex) { + return [point, pointIndex] + }).filter( + function(pointArray, pointIndex) { + return pointActive(pointArray[0], pointIndex) + }) + }); + + titles.enter().append('text') + .style('fill', function (d,i) { + return d.color }) + .style('stroke-opacity', 0) + .style('fill-opacity', 1) + .attr('transform', function(d) { + var dx = nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + Math.sqrt(z(getSize(d[0],d[1]))/Math.PI) + 2; + return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')'; + }) + .text(function(d,i){ + return d[0].label;}); + + titles.exit().remove(); + groups.exit().selectAll('path.nv-label') + .watchTransition(renderWatch, 'scatter exit') + .attr('transform', function(d) { + var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2; + return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'; + }) + .remove(); + titles.each(function(d) { + d3.select(this) + .classed('nv-label', true) + .classed('nv-label-' + d[1], false) + .classed('hover',false); + }); + titles.watchTransition(renderWatch, 'scatter labels') + .attr('transform', function(d) { + var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2; + return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')' + }); + } // Delay updating the invisible interactive layer for smoother animation if( interactiveUpdateDelay ) @@ -11758,7 +12177,7 @@ nv.models.scatter = function() { showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}}, id: {get: function(){return id;}, set: function(_){id=_;}}, interactiveUpdateDelay: {get:function(){return interactiveUpdateDelay;}, set: function(_){interactiveUpdateDelay=_;}}, - + showLabels: {get: function(){return showLabels;}, set: function(_){ showLabels = _;}}, // simple functor options x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, @@ -11826,6 +12245,7 @@ nv.models.scatterChart = function() { , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') , noData = null , duration = 250 + , showLabels = false ; scatter.xScale(x).yScale(y); @@ -11947,7 +12367,9 @@ nv.models.scatterChart = function() { } // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { var legendWidth = availableWidth; legend.width(legendWidth); @@ -11973,7 +12395,8 @@ nv.models.scatterChart = function() { .color(data.map(function(d,i) { d.color = d.color || color(d, i); return d.color; - }).filter(function(d,i) { return !data[i].disabled })); + }).filter(function(d,i) { return !data[i].disabled })) + .showLabels(showLabels); wrap.select('.nv-scatterWrap') .datum(data.filter(function(d) { return !d.disabled })) @@ -12151,6 +12574,7 @@ nv.models.scatterChart = function() { defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, noData: {get: function(){return noData;}, set: function(_){noData=_;}}, duration: {get: function(){return duration;}, set: function(_){duration=_;}}, + showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}}, // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ @@ -12197,6 +12621,8 @@ nv.models.sparkline = function() { , yDomain , xRange , yRange + , showMinMaxPoints = true + , showCurrentPoint = true , dispatch = d3.dispatch('renderEnd') ; @@ -12257,7 +12683,7 @@ nv.models.sparkline = function() { var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), minPoint = pointIndex(yValues.indexOf(y.domain()[0])), currentPoint = pointIndex(yValues.length - 1); - return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); + return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;}); }); points.enter().append('circle'); points.exit().remove(); @@ -12283,15 +12709,17 @@ nv.models.sparkline = function() { chart._options = Object.create({}, { // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - animate: {get: function(){return animate;}, set: function(_){animate=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + animate: {get: function(){return animate;}, set: function(_){animate=_;}}, + showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}}, + showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}}, //functor options x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, @@ -12646,7 +13074,6 @@ nv.models.stackedArea = function() { .y(function(d) { if (d.display !== undefined) { return d.display.y + d.display.y0; } }) - .forceY([0]) .color(data.map(function(d,i) { d.color = d.color || color(d, d.seriesIndex); return d.color; @@ -13021,7 +13448,9 @@ nv.models.stackedAreaChart = function() { g.select("rect").attr("width",availableWidth).attr("height",availableHeight); // Legend - if (showLegend) { + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; legend.width(legendWidth); @@ -13037,7 +13466,9 @@ nv.models.stackedAreaChart = function() { } // Controls - if (showControls) { + if (!showControls) { + g.select('.nv-controlsWrap').selectAll('*').remove(); + } else { var controlsData = [ { key: controlLabels.stacked || 'Stacked', @@ -13231,7 +13662,7 @@ nv.models.stackedAreaChart = function() { key: series.key, value: tooltipValue, color: color(series,series.seriesIndex), - stackedValue: point.display + point: point }); if (showTotalInTooltip && stacked.style() != 'expand') { @@ -13250,8 +13681,8 @@ nv.models.stackedAreaChart = function() { //To handle situation where the stacked area chart is negative, we need to use absolute values //when checking if the mouse Y value is within the stack area. yValue = Math.abs(yValue); - var stackedY0 = Math.abs(series.stackedValue.y0); - var stackedY = Math.abs(series.stackedValue.y); + var stackedY0 = Math.abs(series.point.display.y0); + var stackedY = Math.abs(series.point.display.y); if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0)) { indexToHighlight = i; @@ -13423,70 +13854,208 @@ nv.models.sunburst = function() { //------------------------------------------------------------ var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null + , width = 600 + , height = 600 , mode = "count" - , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }} + , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }} , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one , container = null , color = nv.utils.defaultColor() + , showLabels = false + , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}} + , labelThreshold = 0.02 + , sort = function(d1, d2){return d1.name > d2.name;} + , key = function(d,i){return d.name;} , groupColorByParent = true , duration = 500 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd') - ; + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd'); + + //============================================================ + // aux functions and setup + //------------------------------------------------------------ var x = d3.scale.linear().range([0, 2 * Math.PI]); var y = d3.scale.sqrt(); - var partition = d3.layout.partition() - .sort(null) - .value(function(d) { return 1; }); + var partition = d3.layout.partition().sort(sort); + + var node, availableWidth, availableHeight, radius; + var prevPositions = {}; var arc = d3.svg.arc() - .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) - .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) - .innerRadius(function(d) { return Math.max(0, y(d.y)); }) - .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); + .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) }) + .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) }) + .innerRadius(function(d) {return Math.max(0, y(d.y)) }) + .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) }); + + function rotationToAvoidUpsideDown(d) { + var centerAngle = computeCenterAngle(d); + if(centerAngle > 90){ + return 180; + } + else { + return 0; + } + } + + function computeCenterAngle(d) { + var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); + var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); + var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90; + return centerAngle; + } + + function labelThresholdMatched(d) { + var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); + var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); + + var size = endAngle - startAngle; + return size > labelThreshold; + } + + // When zooming: interpolate the scales. + function arcTweenZoom(e,i) { + var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]), + yd = d3.interpolate(y.domain(), [node.y, 1]), + yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]); - // Keep track of the current and previous node being displayed as the root. - var node, prevNode; - // Keep track of the root node - var rootNode; + if (i === 0) { + return function() {return arc(e);} + } + else { + return function (t) { + x.domain(xd(t)); + y.domain(yd(t)).range(yr(t)); + return arc(e); + } + }; + } + + function arcTweenUpdate(d) { + var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d); + + return function (t) { + var b = ipo(t); + + d.x0 = b.x; + d.dx0 = b.dx; + d.y0 = b.y; + d.dy0 = b.dy; + + return arc(b); + }; + } + + function updatePrevPosition(node) { + var k = key(node); + if(! prevPositions[k]) prevPositions[k] = {}; + var pP = prevPositions[k]; + pP.dx = node.dx; + pP.x = node.x; + pP.dy = node.dy; + pP.y = node.y; + } + + function storeRetrievePrevPositions(nodes) { + nodes.forEach(function(n){ + var k = key(n); + var pP = prevPositions[k]; + //console.log(k,n,pP); + if( pP ){ + n.dx0 = pP.dx; + n.x0 = pP.x; + n.dy0 = pP.dy; + n.y0 = pP.y; + } + else { + n.dx0 = n.dx; + n.x0 = n.x; + n.dy0 = n.dy; + n.y0 = n.y; + } + updatePrevPosition(n); + }); + } + + function zoomClick(d) { + var labels = container.selectAll('text') + var path = container.selectAll('path') + + // fade out all text elements + labels.transition().attr("opacity",0); + + // to allow reference to the new center node + node = d; + + path.transition() + .duration(duration) + .attrTween("d", arcTweenZoom) + .each('end', function(e) { + // partially taken from here: http://bl.ocks.org/metmajer/5480307 + // check if the animated element's data e lies within the visible angle span given in d + if(e.x >= d.x && e.x < (d.x + d.dx) ){ + if(e.depth >= d.depth){ + // get a selection of the associated text element + var parentNode = d3.select(this.parentNode); + var arcText = parentNode.select('text'); + + // fade in the text element and recalculate positions + arcText.transition().duration(duration) + .text( function(e){return labelFormat(e) }) + .attr("opacity", function(d){ + if(labelThresholdMatched(d)) { + return 1; + } + else { + return 0; + } + }) + .attr("transform", function() { + var width = this.getBBox().width; + if(e.depth === 0) + return "translate(" + (width / 2 * - 1) + ",0)"; + else if(e.depth === d.depth){ + return "translate(" + (y(e.y) + 5) + ",0)"; + } + else { + var centerAngle = computeCenterAngle(e); + var rotation = rotationToAvoidUpsideDown(e); + if (rotation === 0) { + return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)'; + } + else { + return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')'; + } + } + }); + } + } + }) + } //============================================================ // chart function //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); function chart(selection) { renderWatch.reset(); + selection.each(function(data) { container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin); - var availableHeight = nv.utils.availableHeight(height, container, margin); - var radius = Math.min(availableWidth, availableHeight) / 2; - var path; + availableWidth = nv.utils.availableWidth(width, container, margin); + availableHeight = nv.utils.availableHeight(height, container, margin); + radius = Math.min(availableWidth, availableHeight) / 2; - nv.utils.initSVG(container); + y.range([0, radius]); // Setup containers and skeleton of chart - var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id); - - var g = wrapEnter.selectAll('nv-sunburst'); - - chart.update = function() { - if ( duration === 0 ) { - container.call(chart); - } else { - container.transition().duration(duration).call(chart); - } - }; - chart.container = this; - - - wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst'); + if( !wrap[0][0] ) { + wrap = container.append('g') + .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id) + .attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + } container.on('click', function (d, i) { dispatch.chartClick({ @@ -13497,13 +14066,21 @@ nv.models.sunburst = function() { }); }); - y.range([0, radius]); - - node = node || data; - rootNode = data[0]; partition.value(modes[mode] || modes["count"]); - path = g.data(partition.nodes).enter() - .append("path") + + //reverse the drawing order so that the labels of inner + //arcs are drawn on top of the outer arcs. + var nodes = partition.nodes(data[0]).reverse() + + storeRetrievePrevPositions(nodes); + var cG = wrap.selectAll('.arc-container').data(nodes, key) + + //handle new datapoints + var cGE = cG.enter() + .append("g") + .attr("class",'arc-container') + + cGE.append("path") .attr("d", arc) .style("fill", function (d) { if (d.color) { @@ -13517,22 +14094,7 @@ nv.models.sunburst = function() { } }) .style("stroke", "#FFF") - .on("click", function(d) { - if (prevNode !== node && node !== d) prevNode = node; - node = d; - path.transition() - .duration(duration) - .attrTween("d", arcTweenZoom(d)); - }) - .each(stash) - .on("dblclick", function(d) { - if (prevNode.parent == d) { - path.transition() - .duration(duration) - .attrTween("d", arcTweenZoom(rootNode)); - } - }) - .each(stash) + .on("click", zoomClick) .on('mouseover', function(d,i){ d3.select(this).classed('hover', true).style('opacity', 0.8); dispatch.elementMouseover({ @@ -13552,58 +14114,68 @@ nv.models.sunburst = function() { }); }); + ///Iterating via each and selecting based on the this + ///makes it work ... a cG.selectAll('path') doesn't. + ///Without iteration the data (in the element) didn't update. + cG.each(function(d){ + d3.select(this).select('path') + .transition() + .duration(duration) + .attrTween('d', arcTweenUpdate); + }); + if(showLabels){ + //remove labels first and add them back + cG.selectAll('text').remove(); - // Setup for switching data: stash the old values for transition. - function stash(d) { - d.x0 = d.x; - d.dx0 = d.dx; + //this way labels are on top of newly added arcs + cG.append('text') + .text( function(e){ return labelFormat(e)}) + .transition() + .duration(duration) + .attr("opacity", function(d){ + if(labelThresholdMatched(d)) { + return 1; + } + else { + return 0; + } + }) + .attr("transform", function(d) { + var width = this.getBBox().width; + if(d.depth === 0){ + return "rotate(0)translate(" + (width / 2 * -1) + ",0)"; + } + else { + var centerAngle = computeCenterAngle(d); + var rotation = rotationToAvoidUpsideDown(d); + if (rotation === 0) { + return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)'; + } + else { + return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')'; + } + } + }); } - // When switching data: interpolate the arcs in data space. - function arcTweenData(a, i) { - var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a); - - function tween(t) { - var b = oi(t); - a.x0 = b.x; - a.dx0 = b.dx; - return arc(b); - } + //zoom out to the center when the data is updated. + zoomClick(nodes[nodes.length - 1]) - if (i == 0) { - // If we are on the first arc, adjust the x domain to match the root node - // at the current zoom level. (We only need to do this once.) - var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]); - return function (t) { - x.domain(xd(t)); - return tween(t); - }; - } else { - return tween; - } - } - - // When zooming: interpolate the scales. - function arcTweenZoom(d) { - var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), - yd = d3.interpolate(y.domain(), [d.y, 1]), - yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); - return function (d, i) { - return i - ? function (t) { - return arc(d); - } - : function (t) { - x.domain(xd(t)); - y.domain(yd(t)).range(yr(t)); - return arc(d); - }; - }; - } + //remove unmatched elements ... + cG.exit() + .transition() + .duration(duration) + .attr('opacity',0) + .each('end',function(d){ + var k = key(d); + prevPositions[k] = undefined; + }) + .remove(); }); + renderWatch.renderEnd('sunburst immediate'); return chart; } @@ -13623,7 +14195,11 @@ nv.models.sunburst = function() { id: {get: function(){return id;}, set: function(_){id=_;}}, duration: {get: function(){return duration;}, set: function(_){duration=_;}}, groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}}, - + showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}}, + labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}}, + labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}}, + sort: {get: function(){return sort;}, set: function(_){sort=_}}, + key: {get: function(){return key;}, set: function(_){key=_}}, // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ margin.top = _.top != undefined ? _.top : margin.top; @@ -13657,21 +14233,19 @@ nv.models.sunburstChart = function() { , defaultState = null , noData = null , duration = 250 - , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') - ; + , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd'); - tooltip.duration(0); //============================================================ // Private Variables //------------------------------------------------------------ var renderWatch = nv.utils.renderWatch(dispatch); + tooltip + .duration(0) .headerEnabled(false) - .valueFormatter(function(d, i) { - return d; - }); + .valueFormatter(function(d){return d;}); //============================================================ // Chart function @@ -13683,11 +14257,11 @@ nv.models.sunburstChart = function() { selection.each(function(data) { var container = d3.select(this); + nv.utils.initSVG(container); - var that = this; - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + var availableWidth = nv.utils.availableWidth(width, container, margin); + var availableHeight = nv.utils.availableHeight(height, container, margin); chart.update = function() { if (duration === 0) { @@ -13696,7 +14270,7 @@ nv.models.sunburstChart = function() { container.transition().duration(duration).call(chart); } }; - chart.container = this; + chart.container = container; // Display No Data message if there's nothing to show. if (!data || !data.length) { @@ -13706,20 +14280,8 @@ nv.models.sunburstChart = function() { container.selectAll('.nv-noData').remove(); } - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-sunburstWrap'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Main Chart Component(s) sunburst.width(availableWidth).height(availableHeight); - var sunWrap = g.select('.nv-sunburstWrap').datum(data); - d3.transition(sunWrap).call(sunburst); - + container.call(sunburst); }); renderWatch.renderEnd('sunburstChart immediate'); @@ -13731,9 +14293,9 @@ nv.models.sunburstChart = function() { //------------------------------------------------------------ sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { + evt.series = { key: evt.data.name, - value: evt.data.size, + value: (evt.data.value || evt.data.size), color: evt.color }; tooltip.data(evt).hidden(false); @@ -13783,7 +14345,8 @@ nv.models.sunburstChart = function() { nv.utils.inheritOptions(chart, sunburst); nv.utils.initOptions(chart); return chart; + }; -nv.version = "1.8.2"; +nv.version = "1.8.3"; })();
\ No newline at end of file |