vendor/assets/javascripts/nvd3.js in chart-0.1.4.8 vs vendor/assets/javascripts/nvd3.js in chart-0.1.4.9

- old
+ new

@@ -1,6 +1,6 @@ -/* nvd3 version 1.8.3 (https://github.com/novus/nvd3) 2016-04-26 */ +/* nvd3 version 1.8.4 (https://github.com/novus/nvd3) 2016-07-03 */ (function(){ // set up main nv object var nv = {}; @@ -152,35 +152,35 @@ } if (typeof(window) !== 'undefined') { window.nv = nv; } -/* Facade for queueing DOM write operations - * with Fastdom (https://github.com/wilsonpage/fastdom) - * if available. - * This could easily be extended to support alternate - * implementations in the future. - */ -nv.dom.write = function(callback) { - if (window.fastdom !== undefined) { - return fastdom.mutate(callback); - } - return callback(); -}; - -/* Facade for queueing DOM read operations - * with Fastdom (https://github.com/wilsonpage/fastdom) - * if available. - * This could easily be extended to support alternate - * implementations in the future. - */ -nv.dom.read = function(callback) { - if (window.fastdom !== undefined) { - return fastdom.measure(callback); - } - return callback(); -}; +/* Facade for queueing DOM write operations + * with Fastdom (https://github.com/wilsonpage/fastdom) + * if available. + * This could easily be extended to support alternate + * implementations in the future. + */ +nv.dom.write = function(callback) { + if (window.fastdom !== undefined) { + return fastdom.mutate(callback); + } + return callback(); +}; + +/* Facade for queueing DOM read operations + * with Fastdom (https://github.com/wilsonpage/fastdom) + * if available. + * This could easily be extended to support alternate + * implementations in the future. + */ +nv.dom.read = function(callback) { + if (window.fastdom !== undefined) { + return fastdom.measure(callback); + } + return callback(); +}; /* 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. dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over @@ -196,11 +196,11 @@ , xScale = d3.scale.linear() , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick', 'elementMouseDown', 'elementMouseUp') , showGuideLine = true , svgContainer = null // Must pass the chart's svg, we'll use its mousemove event. , tooltip = nv.models.tooltip() - , isMSIE = "ActiveXObject" in window // Checkt if IE by looking for activeX. + , isMSIE = window.ActiveXObject// Checkt if IE by looking for activeX. (excludes IE11) ; tooltip .duration(0) .hideDelay(0) @@ -543,35 +543,20 @@ , data = null , gravity = 'w' // Can be 'n','s','e','w'. Determines how tooltip is positioned. , distance = 25 // Distance to offset tooltip from the mouse location. , snapDistance = 0 // Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect) , classes = null // Attaches additional CSS classes to the tooltip DIV that is created. - , chartContainer = null // Parent dom element of the SVG that holds the chart. , hidden = true // Start off hidden, toggle with hide/show functions below. , hideDelay = 200 // Delay (in ms) before the tooltip hides after calling hide(). , tooltip = null // d3 select of the tooltip div. , lastPosition = { left: null, top: null } // Last position the tooltip was in. , enabled = true // True -> tooltips are rendered. False -> don't render tooltips. , duration = 100 // Tooltip movement duration, in ms. , headerEnabled = true // If is to show the tooltip header. , nvPointerEventsClass = "nv-pointer-events-none" // CSS class to specify whether element should not have mouse events. ; - /* - Function that returns the position (relative to the viewport) the tooltip should be placed in. - Should return: { - left: <leftPos>, - top: <topPos> - } - */ - var position = function() { - return { - left: d3.event !== null ? d3.event.clientX : 0, - top: d3.event !== null ? d3.event.clientY : 0 - }; - }; - // Format function for the tooltip values column. var valueFormatter = function(d, i) { return d; }; @@ -627,10 +612,14 @@ trowEnter.append("td") .classed("value",true) .html(function(p, i) { return valueFormatter(p.value, i) }); + trowEnter.filter(function (p,i) { return p.percent !== undefined }).append("td") + .classed("percent", true) + .html(function(p, i) { return "(" + d3.format('%')(p.percent) + ")" }); + trowEnter.selectAll("td").each(function(p) { if (p.highlight) { var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]); var opacity = 0.6; d3.select(this) @@ -645,10 +634,35 @@ html += "<div class='footer'>" + d.footer + "</div>"; return html; }; + /* + Function that returns the position (relative to the viewport/document.body) + the tooltip should be placed in. + Should return: { + left: <leftPos>, + top: <topPos> + } + */ + var position = function() { + var pos = { + left: d3.event !== null ? d3.event.clientX : 0, + top: d3.event !== null ? d3.event.clientY : 0 + }; + + if(getComputedStyle(document.body).transform != 'none') { + // Take the offset into account, as now the tooltip is relative + // to document.body. + var client = document.body.getBoundingClientRect(); + pos.left -= client.left; + pos.top -= client.top; + } + + return pos; + }; + var dataSeriesExists = function(d) { if (d && d.series) { if (nv.utils.isArray(d.series)) { return true; } @@ -732,11 +746,11 @@ .duration(0) .style('opacity', 0); } else { // using tooltip.style('transform') returns values un-usable for tween var old_translate = 'translate(' + lastPosition.left + 'px, ' + lastPosition.top + 'px)'; - var new_translate = 'translate(' + left + 'px, ' + top + 'px)'; + var new_translate = 'translate(' + Math.round(left) + 'px, ' + Math.round(top) + 'px)'; var translateInterpolator = d3.interpolateString(old_translate, new_translate); var is_hidden = tooltip.style('opacity') < 0.1; tooltip .interrupt() // cancel running transitions @@ -760,15 +774,14 @@ }; // Creates new tooltip container, or uses existing one on DOM. function initTooltip() { if (!tooltip || !tooltip.node()) { - var container = chartContainer ? chartContainer : document.body; // Create new tooltip div if it doesn't exist on DOM. var data = [1]; - tooltip = d3.select(container).selectAll('.nvtooltip').data(data); + tooltip = d3.select(document.body).selectAll('.nvtooltip').data(data); tooltip.enter().append('div') .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip")) .attr("id", id) .style("top", 0).style("left", 0) @@ -810,21 +823,24 @@ duration: {get: function(){return duration;}, set: function(_){duration=_;}}, gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, distance: {get: function(){return distance;}, set: function(_){distance=_;}}, snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}}, classes: {get: function(){return classes;}, set: function(_){classes=_;}}, - chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}}, enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}}, hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}}, contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}}, valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}}, headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}}, keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}}, position: {get: function(){return position;}, set: function(_){position=_;}}, // Deprecated options + chartContainer: {get: function(){return document.body;}, set: function(_){ + // deprecated after 1.8.3 + nv.deprecated('chartContainer', 'feature removed after 1.8.3'); + }}, fixedTop: {get: function(){return null;}, set: function(_){ // deprecated after 1.8.1 nv.deprecated('fixedTop', 'feature removed after 1.8.1'); }}, offset: {get: function(){return {left: 0, top: 0};}, set: function(_){ @@ -1548,11 +1564,11 @@ return true; if (!array1 || !array2) return false; - // compare lengths - can save a lot of time + // compare lengths - can save a lot of time if (array1.length != array2.length) return false; for (var i = 0, l = array1.length; i < l; i++) { @@ -1565,11 +1581,12 @@ // Warning - two different object instances will never be equal: {x:20} != {x:20} return false; } } return true; -};nv.models.axis = function() { +}; +nv.models.axis = function() { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ @@ -1690,11 +1707,11 @@ var textHeight = 0; 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', ''); + xTicks.attr('transform', ''); //Calculate the longest xTick width xTicks.each(function(d,i){ var box = this.getBoundingClientRect(); var width = box.width; textHeight = box.height; @@ -1763,11 +1780,11 @@ case 'right': axisLabel.enter().append('text').attr('class', 'nv-axislabel'); axisLabel .style('text-anchor', rotateYLabel ? 'middle' : 'begin') .attr('transform', rotateYLabel ? 'rotate(90)' : '') - .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12 - (axisLabelDistance || 0)) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding()); if (showMaxMin) { axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') .data(scale.domain()); axisMaxMin.enter().append('g').attr('class',function(d,i){ @@ -1898,13 +1915,13 @@ The filter needs to return only ticks at or near zero. Numbers like 0.00001 need to count as zero as well, and the arithmetic trick below solves that. */ return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined) - }) + }) .classed('zero', true); - + //store old scales for use in transitions on update scale0 = scale.copy(); }); @@ -2531,13 +2548,15 @@ var margin = {top: 0, right: 0, bottom: 0, left: 0} , orient = 'left' // TODO top & bottom , reverse = false , ranges = function(d) { return d.ranges } , markers = function(d) { return d.markers ? d.markers : [] } + , markerLines = function(d) { return d.markerLines ? d.markerLines : [0] } , measures = function(d) { return d.measures } , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } + , markerLineLabels = function(d) { return d.markerLineLabels ? d.markerLineLabels : [] } , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) , width = 380 , height = 30 , container = null @@ -2565,18 +2584,21 @@ container = d3.select(this); nv.utils.initSVG(container); var rangez = ranges.call(this, d, i).slice(), markerz = markers.call(this, d, i).slice(), + markerLinez = markerLines.call(this, d, i).slice().sort(d3.descending), measurez = measures.call(this, d, i).slice(), rangeLabelz = rangeLabels.call(this, d, i).slice(), markerLabelz = markerLabels.call(this, d, i).slice(), + markerLineLabelz = markerLineLabels.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(markerLineLabelz, markerLinez); sortLabels(measureLabelz, measurez); // sort values descending rangez.sort(d3.descending); markerz.sort(d3.descending); @@ -2700,10 +2722,52 @@ g.selectAll("path.nv-markerTriangle") .data(markerData) .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }); + var markerLinesData = markerLinez.map( function(marker, index) { + return {value: marker, label: markerLineLabelz[index]} + }); + gEnter + .selectAll("path.nv-markerLine") + .data(markerLinesData) + .enter() + .append('line') + .attr('cursor', '') + .attr('class', 'nv-markerLine') + .attr('x1', function(d) { return x1(d.value) }) + .attr('y1', '2') + .attr('x2', function(d) { return x1(d.value) }) + .attr('y2', availableHeight - 2) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill"), + pos: [x1(d.value), availableHeight/2] + }) + + }) + .on('mousemove', function(d) { + dispatch.elementMousemove({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }) + .on('mouseout', function(d, i) { + dispatch.elementMouseout({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }); + + g.selectAll("path.nv-markerLines") + .data(markerLinesData) + .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }); + wrap.selectAll('.nv-range') .on('mouseover', function(d,i) { var label = rangeLabelz[i] || defaultRangeLabels[i]; dispatch.elementMouseover({ value: d, @@ -3427,11 +3491,11 @@ g.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } g.select('.nv-legendWrap') @@ -3682,11 +3746,10 @@ allData[indexToHighlight].highlight = true; } var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); interactiveLayer.tooltip - .chartContainer(that.parentNode) .valueFormatter(function(d,i) { return yAxis.tickFormat()(d); }) .data( { @@ -4226,23 +4289,23 @@ g.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } wrap.select('.nv-legendWrap') .attr('transform', 'translate(0,' + (-margin.top) +')') } - + if (rightAlignYAxis) { g.select(".nv-y.nv-axis") .attr("transform", "translate(" + availableWidth + ",0)"); - } + } // Main Chart Component(s) discretebar .width(availableWidth) .height(availableHeight); @@ -4549,10 +4612,303 @@ //============================================================ return chart; } +nv.models.focus = function(content) { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var content = content || nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , brush = d3.svg.brush() + ; + + var margin = {top: 10, right: 0, bottom: 30, left: 0} + , color = nv.utils.defaultColor() + , width = null + , height = 70 + , showXAxis = true + , showYAxis = false + , rightAlignYAxis = false + , ticks = null + , x + , y + , brushExtent = null + , duration = 250 + , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd') + ; + + content.interactive(false); + content.pointActive(function(d) { return false; }); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(content); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = height - margin.top - margin.bottom; + + chart.update = function() { + if( duration === 0 ) { + container.call( chart ); + } else { + container.transition().duration(duration).call(chart); + } + }; + chart.container = this; + + // Setup Scales + x = content.xScale(); + y = content.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-focus').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + gEnter.append('g').attr('class', 'nv-background').append('rect'); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-contentWrap'); + gEnter.append('g').attr('class', 'nv-brushBackground'); + gEnter.append('g').attr('class', 'nv-x nv-brush'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + content + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled; })); + + var contentWrap = g.select('.nv-contentWrap') + .datum(data.filter(function(d) { return !d.disabled; })); + + d3.transition(contentWrap).call(content); + + // Setup Brush + brush + .x(x) + .on('brush', function() { + onBrush(); + }); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]); + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight); + + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + .attr('height', availableHeight); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + onBrush(); + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + if (showXAxis) { + xAxis.scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + } + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight / 3; + return 'M' + (0.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x(d[0]) - x.range()[0], + rightWidth = availableWidth - x(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + var extent = brush.empty() ? x.domain() : brush.extent(); + + //The brush extent cannot be less than one. If it is, don't update the line chart. + if (Math.abs(extent[0] - extent[1]) <= 1) { + return; + } + + dispatch.brush({extent: extent, brush: brush}); + + updateBrushBG(); + dispatch.onBrush(extent); + } + + + }); + + renderWatch.renderEnd('focus immediate'); + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.content = content; + chart.brush = brush; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + 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=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, + + // 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; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + content.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + content.color(color); + }}, + interpolate: {get: function(){return content.interpolate();}, set: function(_){ + content.interpolate(_); + }}, + xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ + xAxis.tickFormat(_); + }}, + yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ + yAxis.tickFormat(_); + }}, + x: {get: function(){return content.x();}, set: function(_){ + content.x(_); + }}, + y: {get: function(){return content.y();}, set: function(_){ + content.y(_); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( rightAlignYAxis ? 'right' : 'left'); + }}, + }); + + nv.utils.inheritOptions(chart, content); + nv.utils.initOptions(chart); + + return chart; +}; nv.models.forceDirectedGraph = function() { "use strict"; //============================================================ // Public Variables with Default Settings @@ -4749,10 +5105,11 @@ var margin = {top: 5, right: 0, bottom: 5, left: 0} , width = 400 , height = 20 , getKey = function(d) { return d.key } + , keyFormatter = function (d) { return d } , color = nv.utils.getColor() , maxKeyLength = 20 //default value for key lengths , align = true , padding = 28 //define how much space between legend items. - recommend 32 for furious version , rightAlign = true @@ -4900,11 +5257,11 @@ series.classed('nv-disabled', function(d) { return d.userDisabled }); series.exit().remove(); seriesText .attr('fill', setTextColor) - .text(getKey); + .text(function (d) { return keyFormatter(getKey(d)) }); //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) // NEW ALIGNING CODE, TODO: clean up var versPadding; @@ -4919,14 +5276,14 @@ if (align) { var seriesWidths = []; series.each(function(d,i) { var legendText; - if (getKey(d) && (getKey(d).length > maxKeyLength)) { - var trimmedKey = getKey(d).substring(0, maxKeyLength); + if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) { + var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength); legendText = d3.select(this).select('text').text(trimmedKey + "..."); - d3.select(this).append("svg:title").text(getKey(d)); + d3.select(this).append("svg:title").text(keyFormatter(getKey(d))); } else { legendText = d3.select(this).select('text'); } var nodeTextLength; try { @@ -5057,21 +5414,22 @@ chart.dispatch = dispatch; 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=_;}}, - key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, - align: {get: function(){return align;}, set: function(_){align=_;}}, - rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, - maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}}, - padding: {get: function(){return padding;}, set: function(_){padding=_;}}, - updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, - radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, - expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, - vers: {get: function(){return vers;}, set: function(_){vers=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, + keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, + align: {get: function(){return align;}, set: function(_){align=_;}}, + rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, + maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}}, + padding: {get: function(){return padding;}, set: function(_){padding=_;}}, + updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, + radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, + expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, + vers: {get: function(){return vers;}, set: function(_){vers=_;}}, // 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; @@ -5433,11 +5791,11 @@ g.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } wrap.select('.nv-legendWrap') @@ -5524,11 +5882,10 @@ }); }); var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); interactiveLayer.tooltip - .chartContainer(that.parentNode) .valueFormatter(function(d,i) { return yAxis.tickFormat()(d); }) .data({ value: xValue, @@ -5728,10 +6085,11 @@ var margin = {top: 5, right: 0, bottom: 5, left: 0} , width = 400 , height = 20 , getKey = function(d) { return d.key } + , keyFormatter = function (d) { return d } , color = nv.utils.getColor() , maxKeyLength = 20 //default value for key lengths , align = true , padding = 32 //define how much space between legend items. - recommend 32 for furious version , rightAlign = true @@ -5887,24 +6245,24 @@ series.classed('nv-disabled', function(d) { return d.userDisabled }); series.exit().remove(); seriesText .attr('fill', setTextColor) - .text(getKey); + .text(function (d) { return keyFormatter(getKey(d)) }); //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) // NEW ALIGNING CODE, TODO: clean up var legendWidth = 0; if (align) { var seriesWidths = []; series.each(function(d,i) { var legendText; - if (getKey(d) && (getKey(d).length > maxKeyLength)) { - var trimmedKey = getKey(d).substring(0, maxKeyLength); + if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) { + var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength); legendText = d3.select(this).select('text').text(trimmedKey + "..."); - d3.select(this).append("svg:title").text(getKey(d)); + d3.select(this).append("svg:title").text(keyFormatter(getKey(d))); } else { legendText = d3.select(this).select('text'); } var nodeTextLength; try { @@ -6068,21 +6426,22 @@ chart.dispatch = dispatch; 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=_;}}, - key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, - align: {get: function(){return align;}, set: function(_){align=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, + keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, + align: {get: function(){return align;}, set: function(_){align=_;}}, maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}}, - rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, - padding: {get: function(){return padding;}, set: function(_){padding=_;}}, - updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, - radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, - expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, - vers: {get: function(){return vers;}, set: function(_){vers=_;}}, + rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, + padding: {get: function(){return padding;}, set: function(_){padding=_;}}, + updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, + radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, + expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, + vers: {get: function(){return vers;}, set: function(_){vers=_;}}, // 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; @@ -6338,18 +6697,14 @@ , xAxis = nv.models.axis() , yAxis = nv.models.axis() , legend = nv.models.legend() , interactiveLayer = nv.interactiveGuideline() , tooltip = nv.models.tooltip() - , lines2 = nv.models.line() - , x2Axis = nv.models.axis() - , y2Axis = nv.models.axis() - , brush = d3.svg.brush() + , focus = nv.models.focus(nv.models.line()) ; var margin = {top: 30, right: 20, bottom: 50, left: 60} - , margin2 = {top: 0, right: 20, bottom: 20, left: 60} , color = nv.utils.defaultColor() , width = null , height = null , showLegend = true , legendPosition = 'top' @@ -6357,42 +6712,30 @@ , showYAxis = true , rightAlignYAxis = false , useInteractiveGuideline = false , x , y - , x2 - , y2 , focusEnable = false - , focusShowAxisY = false - , focusShowAxisX = true - , focusHeight = 50 - , brushExtent = null , state = nv.utils.state() , defaultState = null , noData = null - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState', 'renderEnd') + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd') , duration = 250 ; // set options on sub-objects for this chart xAxis.orient('bottom').tickPadding(7); yAxis.orient(rightAlignYAxis ? 'right' : 'left'); lines.clipEdge(true).duration(0); - lines2.interactive(false); - // We don't want any points emitted for the focus chart's scatter graph. - lines2.pointActive(function(d) { return false; }); - x2Axis.orient('bottom').tickPadding(5); - y2Axis.orient(rightAlignYAxis ? 'right' : 'left'); - tooltip.valueFormatter(function(d, i) { return yAxis.tickFormat()(d, i); }).headerFormatter(function(d, i) { return xAxis.tickFormat()(d, i); }); - + interactiveLayer.tooltip.valueFormatter(function(d, i) { return yAxis.tickFormat()(d, i); }).headerFormatter(function(d, i) { return xAxis.tickFormat()(d, i); }); @@ -6422,24 +6765,19 @@ }; function chart(selection) { renderWatch.reset(); renderWatch.models(lines); - renderWatch.models(lines2); if (showXAxis) renderWatch.models(xAxis); if (showYAxis) renderWatch.models(yAxis); - if (focusShowAxisX) renderWatch.models(x2Axis); - if (focusShowAxisY) renderWatch.models(y2Axis); selection.each(function(data) { var container = d3.select(this); nv.utils.initSVG(container); var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0), - availableHeight2 = focusHeight - margin2.top - margin2.bottom; - - chart.update = function() { + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + chart.update = function() { if( duration === 0 ) { container.call( chart ); } else { container.transition().duration(duration).call(chart); } @@ -6471,16 +6809,18 @@ return chart; } else { container.selectAll('.nv-noData').remove(); } + /* Update `main' graph on brush update. */ + focus.dispatch.on("onBrush", function(extent) { + onBrush(extent); + }); // Setup Scales x = lines.xScale(); y = lines.yScale(); - x2 = lines2.xScale(); - y2 = lines2.yScale(); // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); var g = wrap.select('g'); @@ -6492,17 +6832,11 @@ focusEnter.append('g').attr('class', 'nv-x nv-axis'); focusEnter.append('g').attr('class', 'nv-y nv-axis'); focusEnter.append('g').attr('class', 'nv-linesWrap'); focusEnter.append('g').attr('class', 'nv-interactive'); - var contextEnter = gEnter.append('g').attr('class', 'nv-context'); - contextEnter.append('g').attr('class', 'nv-background').append('rect'); - contextEnter.append('g').attr('class', 'nv-x nv-axis'); - contextEnter.append('g').attr('class', 'nv-y nv-axis'); - contextEnter.append('g').attr('class', 'nv-linesWrap'); - contextEnter.append('g').attr('class', 'nv-brushBackground'); - contextEnter.append('g').attr('class', 'nv-x nv-brush'); + var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap'); // Legend if (!showLegend) { g.select('.nv-legendWrap').selectAll('*').remove(); } else { @@ -6512,15 +6846,15 @@ .datum(data) .call(legend); if (legendPosition === 'bottom') { wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (availableHeight1) +')'); + .attr('transform', 'translate(0,' + availableHeight +')'); } else if (legendPosition === 'top') { - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); - availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0); + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); } wrap.select('.nv-legendWrap') .attr('transform', 'translate(0,' + (-margin.top) +')'); } @@ -6535,27 +6869,26 @@ //Set up interactive layer if (useInteractiveGuideline) { interactiveLayer .width(availableWidth) - .height(availableHeight1) + .height(availableHeight) .margin({left:margin.left, top:margin.top}) .svgContainer(container) .xScale(x); wrap.select(".nv-interactive").call(interactiveLayer); } g.select('.nv-focus .nv-background rect') .attr('width', availableWidth) - .attr('height', availableHeight1); - + .attr('height', availableHeight); + lines .width(availableWidth) - .height(availableHeight1) + .height(availableHeight) .color(data.map(function(d,i) { return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled; })); var linesWrap = g.select('.nv-linesWrap') .datum(data.filter(function(d) { return !d.disabled; })); @@ -6563,18 +6896,17 @@ // Setup Main (Focus) Axes if (showXAxis) { xAxis .scale(x) ._ticks(nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight1, 0); - + .tickSize(-availableHeight, 0); } if (showYAxis) { yAxis .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) .tickSize( -availableWidth, 0); } //============================================================ // Update Axes @@ -6596,108 +6928,32 @@ .duration(duration) .call(yAxis) ; } } - + g.select('.nv-focus .nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight1 + ')'); + .attr('transform', 'translate(0,' + availableHeight + ')'); - if( !focusEnable ) - { + //============================================================ + // Update Focus + //============================================================ + if(!focusEnable) { linesWrap.call(lines); updateXAxis(); updateYAxis(); - } - else - { - lines2 - .defined(lines.defined()) - .width(availableWidth) - .height(availableHeight2) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled; })); - - g.select('.nv-context') - .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') - .style('display', focusEnable ? 'initial' : 'none') - ; - - var contextLinesWrap = g.select('.nv-context .nv-linesWrap') + } else { + focus.width(availableWidth); + g.select('.nv-focusWrap') + .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')') .datum(data.filter(function(d) { return !d.disabled; })) - ; - - d3.transition(contextLinesWrap).call(lines2); - - - // Setup Brush - brush - .x(x2) - .on('brush', function() { - onBrush(); - }); - - if (brushExtent) brush.extent(brushExtent); - - var brushBG = g.select('.nv-brushBackground').selectAll('g') - .data([brushExtent || brush.extent()]); - - var brushBGenter = brushBG.enter() - .append('g'); - - brushBGenter.append('rect') - .attr('class', 'left') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight2); - - brushBGenter.append('rect') - .attr('class', 'right') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight2); - - var gBrush = g.select('.nv-x.nv-brush') - .call(brush); - gBrush.selectAll('rect') - .attr('height', availableHeight2); - gBrush.selectAll('.resize').append('path').attr('d', resizePath); - - onBrush(); - - g.select('.nv-context .nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight2); - - // Setup Secondary (Context) Axes - if (focusShowAxisX) { - x2Axis - .scale(x2) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight2, 0); - - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y2.range()[0] + ')'); - d3.transition(g.select('.nv-context .nv-x.nv-axis')) - .call(x2Axis); + .call(focus); + var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent(); + if(extent !== null){ + onBrush(extent); } - - if (focusShowAxisY) { - y2Axis - .scale(y2) - ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) ) - .tickSize( -availableWidth, 0); - - d3.transition(g.select('.nv-context .nv-y.nv-axis')) - .call(y2Axis); - } - - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y2.range()[0] + ')'); } - //============================================================ // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ legend.dispatch.on('stateChange', function(newState) { @@ -6714,11 +6970,11 @@ .filter(function(series, i) { series.seriesIndex = i; return !series.disabled && !series.disableTooltip; }) .forEach(function(series,i) { - var extent = focusEnable ? (brush.empty() ? x2.domain() : brush.extent()) : x.domain(); + var extent = focusEnable ? (focus.brush.empty() ? focus.xScale().domain() : focus.brush.extent()) : x.domain(); var currentValues = series.values.filter(function(d,i) { return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; }); pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x()); @@ -6750,11 +7006,10 @@ var defaultValueFormatter = function(d,i) { return d == null ? "N/A" : yAxis.tickFormat()(d); }; interactiveLayer.tooltip - .chartContainer(chart.container.parentNode) .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) .data({ value: chart.x()( singlePoint,pointIndex ), index: pointIndex, series: allData @@ -6798,71 +7053,38 @@ series.disabled = e.disabled[i]; }); state.disabled = e.disabled; } - chart.update(); }); //============================================================ // Functions //------------------------------------------------------------ - + // Taken from crossfilter (http://square.github.com/crossfilter/) function resizePath(d) { var e = +(d == 'e'), x = e ? 1 : -1, - y = availableHeight2 / 3; + y = availableHeight / 3; return 'M' + (0.5 * x) + ',' + y + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + 'V' + (2 * y - 6) + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y) + 'Z' + 'M' + (2.5 * x) + ',' + (y + 8) + 'V' + (2 * y - 8) + 'M' + (4.5 * x) + ',' + (y + 8) + 'V' + (2 * y - 8); } - - - function updateBrushBG() { - if (!brush.empty()) brush.extent(brushExtent); - brushBG - .data([brush.empty() ? x2.domain() : brushExtent]) - .each(function(d,i) { - var leftWidth = x2(d[0]) - x.range()[0], - rightWidth = availableWidth - x2(d[1]); - d3.select(this).select('.left') - .attr('width', leftWidth < 0 ? 0 : leftWidth); - - d3.select(this).select('.right') - .attr('x', x2(d[1])) - .attr('width', rightWidth < 0 ? 0 : rightWidth); - }); - } - - - function onBrush() { - brushExtent = brush.empty() ? null : brush.extent(); - var extent = brush.empty() ? x2.domain() : brush.extent(); - - //The brush extent cannot be less than one. If it is, don't update the line chart. - if (Math.abs(extent[0] - extent[1]) <= 1) { - return; - } - - dispatch.brush({extent: extent, brush: brush}); - - - updateBrushBG(); - + + function onBrush(extent) { // Update Main (Focus) var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') .datum( - data - .filter(function(d) { return !d.disabled; }) + data.filter(function(d) { return !d.disabled; }) .map(function(d,i) { return { key: d.key, area: d.area, classed: d.classed, @@ -6872,18 +7094,15 @@ disableTooltip: d.disableTooltip }; }) ); focusLinesWrap.transition().duration(duration).call(lines); - - + // Update Main (Focus) Axes updateXAxis(); updateYAxis(); } - - }); renderWatch.renderEnd('lineChart immediate'); return chart; } @@ -6908,16 +7127,16 @@ //------------------------------------------------------------ // expose chart's sub-components chart.dispatch = dispatch; chart.lines = lines; - chart.lines2 = lines2; chart.legend = legend; + chart.focus = focus; chart.xAxis = xAxis; - chart.x2Axis = x2Axis; + chart.x2Axis = focus.xAxis chart.yAxis = yAxis; - chart.y2Axis = y2Axis; + chart.y2Axis = focus.yAxis chart.interactiveLayer = interactiveLayer; chart.tooltip = tooltip; chart.state = state; chart.dispatch = dispatch; chart.options = nv.utils.optionsFunc.bind(chart); @@ -6928,64 +7147,65 @@ 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=_;}}, - focusHeight: {get: function(){return height2;}, set: function(_){focusHeight=_;}}, - focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}}, - focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}}, - brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + // Focus options, mostly passed onto focus model. + focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, + focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}}, + focusShowAxisX: {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}}, + focusShowAxisY: {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}}, + brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}}, // options that require extra logic in the setter + focusMargin: {get: function(){return focus.margin}, set: function(_){ + focus.margin.top = _.top !== undefined ? _.top : focus.margin.top; + focus.margin.right = _.right !== undefined ? _.right : focus.margin.right; + focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom; + focus.margin.left = _.left !== undefined ? _.left : focus.margin.left; + }}, 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; }}, duration: {get: function(){return duration;}, set: function(_){ duration = _; renderWatch.reset(duration); lines.duration(duration); + focus.duration(duration); xAxis.duration(duration); - x2Axis.duration(duration); yAxis.duration(duration); - y2Axis.duration(duration); }}, - focusMargin: {get: function(){return margin2;}, set: function(_){ - margin2.top = _.top !== undefined ? _.top : margin2.top; - margin2.right = _.right !== undefined ? _.right : margin2.right; - margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom; - margin2.left = _.left !== undefined ? _.left : margin2.left; - }}, color: {get: function(){return color;}, set: function(_){ color = nv.utils.getColor(_); legend.color(color); lines.color(color); + focus.color(color); }}, interpolate: {get: function(){return lines.interpolate();}, set: function(_){ lines.interpolate(_); - lines2.interpolate(_); + focus.interpolate(_); }}, xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ xAxis.tickFormat(_); - x2Axis.tickFormat(_); + focus.xTickFormat(_); }}, yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ yAxis.tickFormat(_); - y2Axis.tickFormat(_); + focus.yTickFormat(_); }}, x: {get: function(){return lines.x();}, set: function(_){ lines.x(_); - lines2.x(_); + focus.x(_); }}, y: {get: function(){return lines.y();}, set: function(_){ lines.y(_); - lines2.y(_); + focus.y(_); }}, rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ rightAlignYAxis = _; yAxis.orient( rightAlignYAxis ? 'right' : 'left'); }}, @@ -7004,13 +7224,14 @@ return chart; }; nv.models.lineWithFocusChart = function() { return nv.models.lineChart() - .margin({ bottom: 30 }) + .margin({ bottom: 30 }) .focusEnable( true ); -};nv.models.linePlusBarChart = function() { +}; +nv.models.linePlusBarChart = function() { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ @@ -7078,17 +7299,17 @@ //============================================================ // Private Variables //------------------------------------------------------------ var getBarsAxis = function() { - return !switchYAxisOrder + return switchYAxisOrder ? { main: y2Axis, focus: y4Axis } : { main: y1Axis, focus: y3Axis } } var getLinesAxis = function() { - return !switchYAxisOrder + return switchYAxisOrder ? { main: y1Axis, focus: y3Axis } : { main: y2Axis, focus: y4Axis } } var stateGetter = function(data) { @@ -7240,11 +7461,11 @@ } return series; })) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"? availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight; } @@ -7458,10 +7679,11 @@ .filter(function(dataLine) { return !dataLine.disabled; }) .map(function(d,i) { return { area: d.area, fillOpacity: d.fillOpacity, + strokeWidth: d.strokeWidth, key: d.key, values: d.values.filter(function(d,i) { return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; }) } @@ -7701,10 +7923,11 @@ , xDomain , yDomain , xRange , yRange , groupSpacing = 0.1 + , fillOpacity = 0.75 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') ; //============================================================ // Private Variables @@ -7883,11 +8106,11 @@ .classed('hover', function(d) { return d.hover }) .style('fill', function(d,i){ return color(d, i) }) .style('stroke', function(d,i){ return color(d, i) }); groups .style('stroke-opacity', 1) - .style('fill-opacity', 0.75); + .style('fill-opacity', fillOpacity); var bars = groups.selectAll('rect.nv-bar') .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); bars.exit().remove(); @@ -8074,10 +8297,11 @@ clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, id: {get: function(){return id;}, set: function(_){id=_;}}, hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}}, groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, + fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}}, // 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; @@ -8267,11 +8491,11 @@ g.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } g.select('.nv-legendWrap') @@ -8474,11 +8698,10 @@ data: series.values[pointIndex] }); }); interactiveLayer.tooltip - .chartContainer(that.parentNode) .data({ value: xValue, index: pointIndex, series: allData })(); @@ -8609,10 +8832,11 @@ , stacked = false , showValues = false , showBarLabels = false , valuePadding = 60 , groupSpacing = 0.1 + , fillOpacity = 0.75 , valueFormat = d3.format(',.2f') , delay = 1200 , xDomain , yDomain , xRange @@ -8716,11 +8940,11 @@ .classed('hover', function(d) { return d.hover }) .style('fill', function(d,i){ return color(d, i) }) .style('stroke', function(d,i){ return color(d, i) }); groups.watchTransition(renderWatch, 'multibarhorizontal: groups') .style('stroke-opacity', 1) - .style('fill-opacity', .75); + .style('fill-opacity', fillOpacity); var bars = groups.selectAll('g.nv-bar') .data(function(d) { return d.values }); bars.exit().remove(); @@ -8917,11 +9141,12 @@ //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}}, disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, id: {get: function(){return id;}, set: function(_){id=_;}}, valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}}, - groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, + groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, + fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}}, // 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; @@ -9102,11 +9327,11 @@ g.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } g.select('.nv-legendWrap') @@ -9458,11 +9683,11 @@ series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint); return series; })) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } g.select('.legendWrap') @@ -9738,25 +9963,26 @@ data: point, yAxis: series.yAxis == 2 ? yAxis2 : yAxis1 }); }); - interactiveLayer.tooltip - .chartContainer(chart.container.parentNode) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d,i) { + var defaultValueFormatter = function(d,i) { var yAxis = allData[i].yAxis; - return d === null ? "N/A" : yAxis.tickFormat()(d); - }) - .data({ - value: chart.x()( singlePoint,pointIndex ), - index: pointIndex, - series: allData - })(); + return d == null ? "N/A" : yAxis.tickFormat()(d); + }; + interactiveLayer.tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) + .data({ + value: chart.x()( singlePoint,pointIndex ), + index: pointIndex, + series: allData + })(); + interactiveLayer.renderGuideLine(pointXLocation); }); interactiveLayer.dispatch.on("elementMouseout",function(e) { clearHighlights(); @@ -10199,23 +10425,23 @@ var dataValues = data.map(function (d) {return d.values}); if (active.length === 0) { active = data; }; //set all active before first brush call - + 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; })); //Set as true if all values on an axis are missing. // 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]; var max = extent[1]; var onlyUndefinedValues = false; @@ -10288,11 +10514,11 @@ missingValuesline.exit().remove(); missingValuesline.attr("x1", function(d) { return d[0]; }) .attr("y1", function(d) { return d[1]; }) .attr("x2", function(d) { return d[2]; }) .attr("y2", function(d) { return d[3]; }); - + //Add the text "undefined values" under the missing value line missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]); missingValueslineText.append('text').data([undefinedValuesLabel]); missingValueslineText.enter().append('text'); missingValueslineText.exit().remove(); @@ -10354,11 +10580,11 @@ .attr('dy', '-1em') .attr('text-anchor', 'middle') .on("mouseover", function(d, i) { dispatch.elementMouseover({ label: d.tooltip || d.key, - color: d.color + color: d.color }); }) .on("mouseout", function(d, i) { dispatch.elementMouseout({ label: d.tooltip @@ -10445,32 +10671,32 @@ f.extent[0] = brushDomain[0]; } if (visible) y[f.dimension].brush.extent(f.extent); }); - + dimensions.select('.nv-brushBackground') .each(function (d) { d3.select(this).call(y[d.key].brush); }) .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(); }); @@ -10491,13 +10717,13 @@ return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p])); }); if (isActive) active.push(d); return isActive ? null : 'none'; }); - + updateTicks(); - + dispatch.brush({ filters: filters, active: active }); } @@ -10508,27 +10734,27 @@ f.hasNaN = true; if (f.extent[1] < y[f.dimension].domain()[0]) 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]) + 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]); + 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); @@ -10577,11 +10803,11 @@ displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } }, 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 nv.deprecated('dimensions', 'use dimensionData instead'); if (dimensionData.length === 0) { @@ -10597,11 +10823,11 @@ if (dimensionData.length === 0) { _.forEach(function (k) { dimensionData.push({ key: k }) }) } else { _.forEach(function (k, i) { dimensionData[i].key = k }) } - + }}, dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) { // deprecated after 1.8.1 nv.deprecated('dimensionFormats', 'use dimensionData instead'); if (dimensionData.length === 0) { @@ -10650,11 +10876,11 @@ , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd') , controlWidth = function () { return showControls ? 180 : 0 } ; //============================================================ - + //============================================================ // Private Variables //------------------------------------------------------------ var renderWatch = nv.utils.renderWatch(dispatch); @@ -10677,22 +10903,22 @@ } }; 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) + 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 //------------------------------------------------------------ function chart(selection) { @@ -10741,19 +10967,19 @@ nv.utils.noData(chart, container); return chart; } else { container.selectAll('.nv-noData').remove(); } - + //------------------------------------------------------------ // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinatesChart').data([data]); var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinatesChart').append('g'); var g = wrap.select('g'); - + gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap'); gEnter.append('g').attr('class', 'nv-legendWrap'); g.select("rect") .attr("width", availableWidth) @@ -10768,11 +10994,11 @@ g.select('.nv-legendWrap') .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; })) .call(legend); - if (margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } wrap.select('.nv-legendWrap') .attr('transform', 'translate( 0 ,' + (-margin.top) + ')'); @@ -10783,16 +11009,16 @@ parallelCoordinates .width(availableWidth) .height(availableHeight) .dimensionData(dimensionData) .displayBrush(displayBrush); - + var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ') .datum(data); parallelCoordinatesWrap.transition().call(parallelCoordinates); - + //============================================================ // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ //Display reset brush button parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) { @@ -10849,11 +11075,11 @@ parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) { var tp = { key: evt.label, 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; @@ -10878,11 +11104,11 @@ tooltip(); }); //============================================================ // Expose Public Variables //------------------------------------------------------------ - + // expose chart's sub-components chart.dispatch = dispatch; chart.parallelCoordinates = parallelCoordinates; chart.legend = legend; chart.tooltip = tooltip; @@ -10896,11 +11122,11 @@ defaultState: { get: function () { return defaultState; }, set: function (_) { defaultState = _; } }, 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; }, set: function (_) { margin.top = _.top !== undefined ? _.top : margin.top; @@ -10949,10 +11175,11 @@ , startAngle = false , padAngle = false , endAngle = false , cornerRadius = 0 , donutRatio = 0.5 + , duration = 250 , arcsRadius = [] , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') ; var arcs = []; @@ -11088,11 +11315,12 @@ .attr("d", arcsOver[i]); } dispatch.elementMouseover({ data: d.data, index: i, - color: d3.select(this).style("fill") + color: d3.select(this).style("fill"), + percent: (d.endAngle - d.startAngle) / (2 * Math.PI) }); }); ae.on('mouseout', function(d, i) { d3.select(this).classed('hover', false); if (growOnHover) { @@ -11130,10 +11358,11 @@ this._current = d; }); slices.select('path') .transition() + .duration(duration) .attr('d', function (d, i) { return arcs[i](d); }) .attrTween('d', arcTween); if (showLabels) { // This does the normal label @@ -11334,10 +11563,14 @@ margin.top = typeof _.top != 'undefined' ? _.top : margin.top; margin.right = typeof _.right != 'undefined' ? _.right : margin.right; margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; margin.left = typeof _.left != 'undefined' ? _.left : margin.left; }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }}, y: {get: function(){return getY;}, set: function(_){ getY=d3.functor(_); }}, color: {get: function(){return color;}, set: function(_){ color=nv.utils.getColor(_); @@ -11362,10 +11595,11 @@ var tooltip = nv.models.tooltip(); var margin = {top: 30, right: 20, bottom: 20, left: 20} , width = null , height = null + , showTooltipPercent = false , showLegend = true , legendPosition = "top" , color = nv.utils.defaultColor() , state = nv.utils.state() , defaultState = null @@ -11467,11 +11701,11 @@ wrap.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } wrap.select('.nv-legendWrap') @@ -11532,12 +11766,17 @@ pie.dispatch.on('elementMouseover.tooltip', function(evt) { evt['series'] = { key: chart.x()(evt.data), value: chart.y()(evt.data), - color: evt.color + color: evt.color, + percent: evt.percent }; + if (!showTooltipPercent) { + delete evt.percent; + delete evt.series.percent; + } tooltip.data(evt).hidden(false); }); pie.dispatch.on('elementMouseout.tooltip', function(evt) { tooltip.hidden(true); @@ -11559,26 +11798,28 @@ chart.options = nv.utils.optionsFunc.bind(chart); // 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=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, // options that require extra logic in the setter color: {get: function(){return color;}, set: function(_){ color = _; legend.color(color); pie.color(color); }}, duration: {get: function(){return duration;}, set: function(_){ duration = _; renderWatch.reset(duration); + pie.duration(duration); }}, 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; @@ -11630,11 +11871,11 @@ , singlePoint = false , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd') , useVoronoi = true , duration = 250 , interactiveUpdateDelay = 300 - , showLabels = false + , showLabels = false ; //============================================================ // Private Variables @@ -11688,11 +11929,11 @@ point.series = i; }); }); // Setup Scales - var logScale = chart.yScale().name === d3.scale.log().name ? true : false; + 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( data.map(function(d) { return d.values.map(function(d,i) { @@ -11924,18 +12165,20 @@ .on('click', function(d,i) { //nv.log('test', d, i); if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point var series = data[d.series], point = series.values[i]; - + var element = this; dispatch.elementClick({ point: point, series: series, pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], //TODO: make this pos base on the page relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], seriesIndex: d.series, - pointIndex: i + pointIndex: i, + event: d3.event, + element: element }); }) .on('dblclick', function(d,i) { if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point var series = data[d.series], @@ -12046,14 +12289,14 @@ .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 + + // 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] @@ -12062,11 +12305,11 @@ return pointActive(pointArray[0], pointIndex) }) }); titles.enter().append('text') - .style('fill', function (d,i) { + .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; @@ -12375,11 +12618,11 @@ wrap.select('.nv-legendWrap') .datum(data) .call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } wrap.select('.nv-legendWrap') @@ -12629,11 +12872,11 @@ //============================================================ // Private Variables //------------------------------------------------------------ var renderWatch = nv.utils.renderWatch(dispatch); - + function chart(selection) { renderWatch.reset(); selection.each(function(data) { var availableWidth = width - margin.left - margin.right, availableHeight = height - margin.top - margin.bottom; @@ -12694,11 +12937,11 @@ .attr('class', function(d,i) { return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' }); }); - + renderWatch.renderEnd('sparkline immediate'); return chart; } //============================================================ @@ -12764,11 +13007,11 @@ , alignValue = true , rightAlignValue = false , noData = null , dispatch = d3.dispatch('renderEnd') ; - + //============================================================ // Private Variables //------------------------------------------------------------ var renderWatch = nv.utils.renderWatch(dispatch); @@ -13058,17 +13301,17 @@ gEnter.append('g').attr('class', 'nv-areaWrap'); gEnter.append('g').attr('class', 'nv-scatterWrap'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - + // If the user has not specified forceY, make sure 0 is included in the domain // Otherwise, use user-specified values for forceY if (scatter.forceY().length == 0) { scatter.forceY().push(0); } - + scatter .width(availableWidth) .height(availableHeight) .x(getX) .y(function(d) { @@ -13302,10 +13545,11 @@ , yAxis = nv.models.axis() , legend = nv.models.legend() , controls = nv.models.legend() , interactiveLayer = nv.interactiveGuideline() , tooltip = nv.models.tooltip() + , focus = nv.models.focus(nv.models.stackedArea()) ; var margin = {top: 30, right: 25, bottom: 50, left: 60} , width = null , height = null @@ -13313,10 +13557,11 @@ , showControls = true , showLegend = true , showXAxis = true , showYAxis = true , rightAlignYAxis = false + , focusEnable = false , useInteractiveGuideline = false , showTotalInTooltip = true , totalLabel = 'TOTAL' , x //can be accessed via chart.xScale() , y //can be accessed via chart.yScale() @@ -13345,11 +13590,11 @@ interactiveLayer.tooltip .headerFormatter(function(d, i) { return xAxis.tickFormat()(d, i); }) .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); + return d == null ? "N/A" : yAxis.tickFormat()(d, i); }); var oldYTickFormat = null, oldValueFormatter = null; @@ -13394,11 +13639,11 @@ var container = d3.select(this), that = this; nv.utils.initSVG(container); var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); chart.update = function() { container.transition().duration(duration).call(chart); }; chart.container = this; state @@ -13425,42 +13670,45 @@ nv.utils.noData(chart, container) return chart; } else { container.selectAll('.nv-noData').remove(); } - // Setup Scales x = stacked.xScale(); y = stacked.yScale(); // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); var g = wrap.select('g'); - gEnter.append("rect").style("opacity",0); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-stackedWrap'); gEnter.append('g').attr('class', 'nv-legendWrap'); gEnter.append('g').attr('class', 'nv-controlsWrap'); - gEnter.append('g').attr('class', 'nv-interactive'); - g.select("rect").attr("width",availableWidth).attr("height",availableHeight); + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-background').append('rect'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y nv-axis'); + focusEnter.append('g').attr('class', 'nv-stackedWrap'); + focusEnter.append('g').attr('class', 'nv-interactive'); + // g.select("rect").attr("width",availableWidth).attr("height",availableHeight); + + var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap'); + // Legend if (!showLegend) { g.select('.nv-legendWrap').selectAll('*').remove(); } else { var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; legend.width(legendWidth); g.select('.nv-legendWrap').datum(data).call(legend); - if ( margin.top != legend.height()) { + if (legend.height() > margin.top) { margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); } g.select('.nv-legendWrap') .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); } @@ -13507,11 +13755,11 @@ g.select('.nv-controlsWrap') .datum(controlsData) .call(controls); - if ( margin.top != Math.max(controls.height(), legend.height()) ) { + if (Math.max(controls.height(), legend.height()) > margin.top) { margin.top = Math.max(controls.height(), legend.height()); availableHeight = nv.utils.availableHeight(height, container, margin); } g.select('.nv-controlsWrap') @@ -13534,31 +13782,29 @@ .svgContainer(container) .xScale(x); wrap.select(".nv-interactive").call(interactiveLayer); } + g.select('.nv-focus .nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + stacked .width(availableWidth) - .height(availableHeight); + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled; })); - var stackedWrap = g.select('.nv-stackedWrap') - .datum(data); + var stackedWrap = g.select('.nv-focus .nv-stackedWrap') + .datum(data.filter(function(d) { return !d.disabled; })); - stackedWrap.transition().call(stacked); - // Setup Axes if (showXAxis) { xAxis.scale(x) ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) .tickSize( -availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - - g.select('.nv-x.nv-axis') - .transition().duration(0) - .call(xAxis); } if (showYAxis) { var ticks; if (stacked.offset() === 'wiggle') { @@ -13568,11 +13814,28 @@ ticks = nv.utils.calcTicksY(availableHeight/36, data); } yAxis.scale(y) ._ticks(ticks) .tickSize(-availableWidth, 0); + } + //============================================================ + // Update Axes + //============================================================ + function updateXAxis() { + if(showXAxis) { + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')') + .transition() + .duration(duration) + .call(xAxis) + ; + } + } + + function updateYAxis() { + if(showYAxis) { if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { var currentFormat = yAxis.tickFormat(); if ( !oldYTickFormat || currentFormat !== percentFormatter ) oldYTickFormat = currentFormat; @@ -13585,16 +13848,36 @@ yAxis.tickFormat(oldYTickFormat); oldYTickFormat = null; } } - g.select('.nv-y.nv-axis') + g.select('.nv-focus .nv-y.nv-axis') .transition().duration(0) .call(yAxis); + } } //============================================================ + // Update Focus + //============================================================ + if(!focusEnable) { + stackedWrap.transition().call(stacked); + updateXAxis(); + updateYAxis(); + } else { + focus.width(availableWidth); + g.select('.nv-focusWrap') + .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')') + .datum(data.filter(function(d) { return !d.disabled; })) + .call(focus); + var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent(); + if(extent !== null){ + onBrush(extent); + } + } + + //============================================================ // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ stacked.dispatch.on('areaClick.toggle', function(e) { if (data.filter(function(d) { return !d.disabled }).length === 1) @@ -13637,11 +13920,11 @@ chart.update(); }); interactiveLayer.dispatch.on('elementMousemove', function(e) { stacked.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0; + var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true; data .filter(function(series, i) { series.seriesIndex = i; return !series.disabled; }) @@ -13663,12 +13946,13 @@ value: tooltipValue, color: color(series,series.seriesIndex), point: point }); - if (showTotalInTooltip && stacked.style() != 'expand') { + if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) { valueSum += tooltipValue; + allNullValues = false; }; }); allData.reverse(); @@ -13692,11 +13976,11 @@ if (indexToHighlight != null) allData[indexToHighlight].highlight = true; } //If we are not in 'expand' mode, add a 'Total' row to the tooltip. - if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2) { + if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) { allData.push({ key: totalLabel, value: valueSum, total: true }); @@ -13719,11 +14003,10 @@ oldValueFormatter = null; } } interactiveLayer.tooltip - .chartContainer(that.parentNode) .valueFormatter(valueFormatter) .data( { value: xValue, series: allData @@ -13736,10 +14019,15 @@ interactiveLayer.dispatch.on("elementMouseout",function(e) { stacked.clearHighlights(); }); + /* Update `main' graph on brush update. */ + focus.dispatch.on("onBrush", function(extent) { + onBrush(extent); + }); + // Update chart from a state object passed to event handler dispatch.on('changeState', function(e) { if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { data.forEach(function(series,i) { @@ -13755,10 +14043,38 @@ } chart.update(); }); + //============================================================ + // Functions + //------------------------------------------------------------ + + function onBrush(extent) { + // Update Main (Focus) + var stackedWrap = g.select('.nv-focus .nv-stackedWrap') + .datum( + data.filter(function(d) { return !d.disabled; }) + .map(function(d,i) { + return { + key: d.key, + area: d.area, + classed: d.classed, + values: d.values.filter(function(d,i) { + return stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1]; + }), + disableTooltip: d.disableTooltip + }; + }) + ); + stackedWrap.transition().duration(duration).call(stacked); + + // Update Main (Focus) Axes + updateXAxis(); + updateYAxis(); + } + }); renderWatch.renderEnd('stacked Area chart immediate'); return chart; } @@ -13774,24 +14090,26 @@ }); stacked.dispatch.on('elementMouseout.tooltip', function(evt) { tooltip.hidden(true) }); - //============================================================ // Expose Public Variables //------------------------------------------------------------ // expose chart's sub-components chart.dispatch = dispatch; chart.stacked = stacked; chart.legend = legend; chart.controls = controls; chart.xAxis = xAxis; + chart.x2Axis = focus.xAxis; chart.yAxis = yAxis; + chart.y2Axis = focus.yAxis; chart.interactiveLayer = interactiveLayer; chart.tooltip = tooltip; + chart.focus = focus; chart.dispatch = dispatch; chart.options = nv.utils.optionsFunc.bind(chart); chart._options = Object.create({}, { @@ -13806,18 +14124,27 @@ showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}}, showTotalInTooltip: {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}}, totalLabel: {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}}, + focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, + focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}}, + brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}}, // 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; }}, + focusMargin: {get: function(){return focus.margin}, set: function(_){ + focus.margin.top = _.top !== undefined ? _.top : focus.margin.top; + focus.margin.right = _.right !== undefined ? _.right : focus.margin.right; + focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom; + focus.margin.left = _.left !== undefined ? _.left : focus.margin.left; + }}, duration: {get: function(){return duration;}, set: function(_){ duration = _; renderWatch.reset(duration); stacked.duration(duration); xAxis.duration(duration); @@ -13825,11 +14152,20 @@ }}, color: {get: function(){return color;}, set: function(_){ color = nv.utils.getColor(_); legend.color(color); stacked.color(color); + focus.color(color); }}, + x: {get: function(){return stacked.x();}, set: function(_){ + stacked.x(_); + focus.x(_); + }}, + y: {get: function(){return stacked.y();}, set: function(_){ + stacked.y(_); + focus.y(_); + }}, rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ rightAlignYAxis = _; yAxis.orient( rightAlignYAxis ? 'right' : 'left'); }}, useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ @@ -13843,10 +14179,16 @@ nv.utils.inheritOptions(chart, stacked); nv.utils.initOptions(chart); return chart; }; + +nv.models.stackedAreaWithFocusChart = function() { + return nv.models.stackedAreaChart() + .margin({ bottom: 30 }) + .focusEnable( true ); +}; // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad nv.models.sunburst = function() { "use strict"; //============================================================ @@ -13903,10 +14245,16 @@ 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 computeNodePercentage(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))); + return (endAngle - startAngle) / (2 * Math.PI); + } + 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; @@ -14052,11 +14400,13 @@ // Setup containers and skeleton of chart 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 + ')'); + .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')'); + } else { + wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')'); } container.on('click', function (d, i) { dispatch.chartClick({ data: d, @@ -14097,11 +14447,12 @@ .on("click", zoomClick) .on('mouseover', function(d,i){ d3.select(this).classed('hover', true).style('opacity', 0.8); dispatch.elementMouseover({ data: d, - color: d3.select(this).style("fill") + color: d3.select(this).style("fill"), + percent: computeNodePercentage(d) }); }) .on('mouseout', function(d,i){ d3.select(this).classed('hover', false).style('opacity', 1); dispatch.elementMouseout({ @@ -14227,10 +14578,11 @@ var margin = {top: 30, right: 20, bottom: 20, left: 20} , width = null , height = null , color = nv.utils.defaultColor() + , showTooltipPercent = false , id = Math.round(Math.random() * 100000) , defaultState = null , noData = null , duration = 250 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd'); @@ -14278,11 +14630,11 @@ return chart; } else { container.selectAll('.nv-noData').remove(); } - sunburst.width(availableWidth).height(availableHeight); + sunburst.width(availableWidth).height(availableHeight).margin(margin); container.call(sunburst); }); renderWatch.renderEnd('sunburstChart immediate'); return chart; @@ -14294,12 +14646,17 @@ sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { evt.series = { key: evt.data.name, value: (evt.data.value || evt.data.size), - color: evt.color + color: evt.color, + percent: evt.percent }; + if (!showTooltipPercent) { + delete evt.percent; + delete evt.series.percent; + } tooltip.data(evt).hidden(false); }); sunburst.dispatch.on('elementMouseout.tooltip', function(evt) { tooltip.hidden(true); @@ -14320,12 +14677,13 @@ chart.options = nv.utils.optionsFunc.bind(chart); // use Object get/set functionality to map between vars and chart functions chart._options = Object.create({}, { // simple options, just get/set the necessary values - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}}, // options that require extra logic in the setter color: {get: function(){return color;}, set: function(_){ color = _; sunburst.color(color); @@ -14338,15 +14696,16 @@ 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; + sunburst.margin(margin); }} }); nv.utils.inheritOptions(chart, sunburst); nv.utils.initOptions(chart); return chart; }; -nv.version = "1.8.3"; -})(); \ No newline at end of file +nv.version = "1.8.4"; +})();