From db9778e7d5c7df3f390e5c2c1f3d5b9a6dcb2c6d Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 18 Jan 2013 14:18:01 -0500 Subject: [PATCH] Added a 'beforeUpdate' dispatch event to the discreteBarChart --- nv.d3.js | 11142 ++++++++++++++++++------------- nv.d3.min.js | 5 + src/models/discreteBarChart.js | 4 +- 3 files changed, 6554 insertions(+), 4597 deletions(-) diff --git a/nv.d3.js b/nv.d3.js index 2824f9b..c1598be 100644 --- a/nv.d3.js +++ b/nv.d3.js @@ -749,287 +749,337 @@ nv.models.axis = function() { return chart; } -//TODO: consider deprecating and using multibar with single series for this -nv.models.historicalBar = function() { + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ +nv.models.bulletChart = 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.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , forceX = [] - , forceY = [0] - , padData = false - , clipEdge = true - , color = nv.utils.defaultColor() - , xDomain - , yDomain - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + var bullet = nv.models.bullet() + ; + + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

' + x + '

' + + '

' + y + '

' + } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') ; //============================================================ + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left, + top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top, + content = tooltip(e.key, e.label, e.value, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, + selection.each(function(d, i) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, availableHeight = height - margin.top - margin.bottom, - container = d3.select(this); + that = this; - //------------------------------------------------------------ - // Setup Scales + chart.update = function() { chart(selection) }; + chart.container = this; - x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )) + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. - if (padData) - x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range([0, availableWidth]); + if (!d || !ranges.call(this, d, i)) { + var noDataText = container.selectAll('.nv-noData').data([noData]); - y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) - .range([availableHeight, 0]); + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', 18 + margin.top + availableHeight / 2) + .text(function(d) { return d }); - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) - : y.domain([-1,1]); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } //------------------------------------------------------------ + + 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); + + //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-bar').data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bar'); - var defsEnter = wrapEnter.append('defs'); + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); var gEnter = wrapEnter.append('g'); var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-bars'); + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); - g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + // Stash the new scale. + this.__chart__ = x1; + /* + // Derive width-scales from the x-scales. + var w0 = bulletWidth(x0), + w1 = bulletWidth(x1); + function bulletWidth(x) { + var x0 = x(0); + return function(d) { + return Math.abs(x(d) - x(0)); + }; + } - var bars = wrap.select('.nv-bars').selectAll('.nv-bar') - .data(function(d) { return d }); + function bulletTranslate(x) { + return function(d) { + return 'translate(' + x(d) + ',0)'; + }; + } + */ - bars.exit().remove(); + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; - var barsEnter = bars.enter().append('rect') - //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) - .attr('x', 0 ) - .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) - .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) - .on('mouseover', function(d,i) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - point: d, - series: data[0], - pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: 0, - e: d3.event - }); + var title = gEnter.select('.nv-titles').append('g') + .attr('text-anchor', 'end') + .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); + title.append('text') + .attr('class', 'nv-title') + .text(function(d) { return d.title; }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - point: d, - series: data[0], - pointIndex: i, - seriesIndex: 0, - e: d3.event - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - //label: d[label], - value: getY(d,i), - data: d, - index: i, - pos: [x(getX(d,i)), y(getY(d,i))], - e: d3.event, - id: id - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - //label: d[label], - value: getY(d,i), - data: d, - index: i, - pos: [x(getX(d,i)), y(getY(d,i))], - e: d3.event, - id: id - }); - d3.event.stopPropagation(); - }); + title.append('text') + .attr('class', 'nv-subtitle') + .attr('dy', '1em') + .text(function(d) { return d.subtitle; }); - bars - .attr('fill', function(d,i) { return color(d, i); }) - .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) - .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) //TODO: better width calculations that don't assume always uniform data spacing;w - .attr('width', (availableWidth / data[0].values.length) * .9 ) - d3.transition(bars) - //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) - .attr('y', function(d,i) { - return getY(d,i) < 0 ? - y(0) : - y(0) - y(getY(d,i)) < 1 ? - y(0) - 1 : - y(getY(d,i)) - }) - .attr('height', function(d,i) { return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) }); - //.order(); // not sure if this makes any sense for this model + bullet + .width(availableWidth) + .height(availableHeight) - }); + var bulletWrap = g.select('.nv-bulletWrap'); - return chart; - } + d3.transition(bulletWrap).call(bullet); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - chart.dispatch = dispatch; + // Compute the tick format. + var format = tickFormat || x1.tickFormat( availableWidth / 100 ); - chart.x = function(_) { - if (!arguments.length) return getX; - getX = _; - return chart; - }; + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks( availableWidth / 50 ), function(d) { + return this.textContent || format(d); + }); - chart.y = function(_) { - if (!arguments.length) return getY; - getY = _; - return chart; - }; + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); - chart.margin = function(_) { - if (!arguments.length) return margin; - 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; - return chart; - }; + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); + + + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); + + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + e.key = data[0].title; + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + }); + + d3.timer.flush(); - chart.width = function(_) { - if (!arguments.length) return width; - width = _; return chart; - }; + } - chart.height = function(_) { - if (!arguments.length) return height; - height = _; + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bullet.dispatch.on('elementMouseover.tooltip', function(e) { + dispatch.tooltipShow(e); + }); + + bullet.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.bullet = bullet; + + d3.rebind(chart, bullet, 'color'); + + // left, right, top, bottom + chart.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + reverse = orient == 'right' || orient == 'bottom'; return chart; }; - chart.xScale = function(_) { - if (!arguments.length) return x; - x = _; + // ranges (bad, satisfactory, good) + chart.ranges = function(x) { + if (!arguments.length) return ranges; + ranges = x; return chart; }; - chart.yScale = function(_) { - if (!arguments.length) return y; - y = _; + // markers (previous, goal) + chart.markers = function(x) { + if (!arguments.length) return markers; + markers = x; return chart; }; - chart.xDomain = function(_) { - if (!arguments.length) return xDomain; - xDomain = _; + // measures (actual, forecast) + chart.measures = function(x) { + if (!arguments.length) return measures; + measures = x; return chart; }; - chart.yDomain = function(_) { - if (!arguments.length) return yDomain; - yDomain = _; + chart.width = function(x) { + if (!arguments.length) return width; + width = x; return chart; }; - chart.forceX = function(_) { - if (!arguments.length) return forceX; - forceX = _; + chart.height = function(x) { + if (!arguments.length) return height; + height = x; return chart; }; - chart.forceY = function(_) { - if (!arguments.length) return forceY; - forceY = _; + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; return chart; }; - chart.padData = function(_) { - if (!arguments.length) return padData; - padData = _; + chart.tickFormat = function(x) { + if (!arguments.length) return tickFormat; + tickFormat = x; return chart; }; - chart.clipEdge = function(_) { - if (!arguments.length) return clipEdge; - clipEdge = _; + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; return chart; }; - chart.id = function(_) { - if (!arguments.length) return id; - id = _; + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; return chart; }; @@ -1037,7 +1087,9 @@ nv.models.historicalBar = function() { return chart; -} +}; + + // Chart design based on the recommendations of Stephen Few. Implementation // based on the work of Clint Ivy, Jamie Love, and Jason Davies. @@ -1416,34 +1468,45 @@ nv.models.bullet = function() { -// Chart design based on the recommendations of Stephen Few. Implementation -// based on the work of Clint Ivy, Jamie Love, and Jason Davies. -// http://projects.instantcognition.com/protovis/bulletchart/ -nv.models.bulletChart = function() { +nv.models.cumulativeLineChart = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var bullet = nv.models.bullet() + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() ; - var orient = 'left' // TODO top & bottom - , reverse = false - , margin = {top: 5, right: 40, bottom: 20, left: 120} - , ranges = function(d) { return d.ranges } - , markers = function(d) { return d.markers } - , measures = function(d) { return d.measures } + var margin = {top: 30, right: 30, bottom: 50, left: 60} + , color = nv.utils.defaultColor() , width = null - , height = 55 - , tickFormat = null + , height = null + , showLegend = true , tooltips = true + , showControls = true + , rescaleY = true , tooltip = function(key, x, y, e, graph) { - return '

' + x + '

' + - '

' + y + '

' + return '

' + key + '

' + + '

' + y + ' at ' + x + '

' } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , id = lines.id() + , state = { index: 0, rescaleY: rescaleY } , noData = 'No Data Available.' - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') ; //============================================================ @@ -1453,34 +1516,99 @@ nv.models.bulletChart = function() { // Private Variables //------------------------------------------------------------ + var dx = d3.scale.linear() + , index = {i: 0, x: 0} + ; + var showTooltip = function(e, offsetElement) { - var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left, - top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top, - content = tooltip(e.key, e.label, e.value, e, chart); + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); - nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + nv.tooltip.show([left, top], content, null, null, offsetElement); }; - //============================================================ - +/* + //Moved to see if we can get better behavior to fix issue #315 + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); - function chart(selection) { - selection.each(function(d, i) { - var container = d3.select(this); + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + d.x += d3.event.dx; + d.i = Math.round(dx.invert(d.x)); + + d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)'); + chart.update(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + chart.update(); + } +*/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this).classed('nv-chart-' + id, true), + that = this; var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom, - that = this; + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; chart.update = function() { chart(selection) }; chart.container = this; + + + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + index.x = d3.event.x; + index.i = Math.round(dx.invert(index.x)); + updateZero(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + } + + + + //------------------------------------------------------------ // Display No Data message if there's nothing to show. - if (!d || !ranges.call(this, d, i)) { + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { var noDataText = container.selectAll('.nv-noData').data([noData]); noDataText.enter().append('text') @@ -1490,7 +1618,7 @@ nv.models.bulletChart = function() { noDataText .attr('x', margin.left + availableWidth / 2) - .attr('y', 18 + margin.top + availableHeight / 2) + .attr('y', margin.top + availableHeight / 2) .text(function(d) { return d }); return chart; @@ -1501,147 +1629,310 @@ nv.models.bulletChart = function() { //------------------------------------------------------------ + //------------------------------------------------------------ + // Setup Scales - 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); + x = lines.xScale(); + y = lines.yScale(); - //------------------------------------------------------------ - // Setup containers and skeleton of chart + if (!rescaleY) { + var seriesDomains = data + .filter(function(series) { return !series.disabled }) + .map(function(series,i) { + var initialDomain = d3.extent(series.values, lines.y()); - var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + //account for series being disabled when losing 95% or more + if (initialDomain[0] < -.95) initialDomain[0] = -.95; - gEnter.append('g').attr('class', 'nv-bulletWrap'); - gEnter.append('g').attr('class', 'nv-titles'); + return [ + (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), + (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) + ]; + }); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + var completeDomain = [ + d3.min(seriesDomains, function(d) { return d[0] }), + d3.max(seriesDomains, function(d) { return d[1] }) + ] - //------------------------------------------------------------ + lines.yDomain(completeDomain); + } else { + lines.yDomain(null); + } - // Compute the new x-scale. - var x1 = d3.scale.linear() - .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain - .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length + .range([0, availableWidth]) + .clamp(true); - // Retrieve the old x-scale, if this is an update. - var x0 = this.__chart__ || d3.scale.linear() - .domain([0, Infinity]) - .range(x1.range()); + //------------------------------------------------------------ - // Stash the new scale. - this.__chart__ = x1; - /* - // Derive width-scales from the x-scales. - var w0 = bulletWidth(x0), - w1 = bulletWidth(x1); + var data = indexify(index.i, data); - function bulletWidth(x) { - var x0 = x(0); - return function(d) { - return Math.abs(x(d) - x(0)); - }; - } - function bulletTranslate(x) { - return function(d) { - return 'translate(' + x(d) + ',0)'; - }; - } - */ + //------------------------------------------------------------ + // Setup containers and skeleton of chart - var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) - w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var g = wrap.select('g'); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-background'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); - var title = gEnter.select('.nv-titles').append('g') - .attr('text-anchor', 'end') - .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); - title.append('text') - .attr('class', 'nv-title') - .text(function(d) { return d.title; }); + //------------------------------------------------------------ - title.append('text') - .attr('class', 'nv-subtitle') - .attr('dy', '1em') - .text(function(d) { return d.subtitle; }); + //------------------------------------------------------------ + // Legend + if (showLegend) { + legend.width(availableWidth); - bullet - .width(availableWidth) - .height(availableHeight) + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - var bulletWrap = g.select('.nv-bulletWrap'); + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } - d3.transition(bulletWrap).call(bullet); + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + //------------------------------------------------------------ - // Compute the tick format. - var format = tickFormat || x1.tickFormat( availableWidth / 100 ); + //------------------------------------------------------------ + // Controls - // Update the tick groups. - var tick = g.selectAll('g.nv-tick') - .data(x1.ticks( availableWidth / 50 ), function(d) { - return this.textContent || format(d); - }); + if (showControls) { + var controlsData = [ + { key: 'Re-scale y-axis', disabled: !rescaleY } + ]; - // Initialize the ticks with the old scale, x0. - var tickEnter = tick.enter().append('g') - .attr('class', 'nv-tick') - .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) - .style('opacity', 1e-6); + controls.width(140).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } - tickEnter.append('line') - .attr('y1', availableHeight) - .attr('y2', availableHeight * 7 / 6); + //------------------------------------------------------------ - tickEnter.append('text') - .attr('text-anchor', 'middle') - .attr('dy', '1em') - .attr('y', availableHeight * 7 / 6) - .text(format); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - // Transition the updating ticks to the new scale, x1. - var tickUpdate = d3.transition(tick) - .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) - .style('opacity', 1); - tickUpdate.select('line') - .attr('y1', availableHeight) - .attr('y2', availableHeight * 7 / 6); + // Show error if series goes below 100% + var tempDisabled = data.filter(function(d) { return d.tempDisabled }); - tickUpdate.select('text') - .attr('y', availableHeight * 7 / 6); + wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates + if (tempDisabled.length) { + wrap.append('text').attr('class', 'tempDisabled') + .attr('x', availableWidth / 2) + .attr('y', '-.71em') + .style('text-anchor', 'end') + .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + } - // Transition the exiting ticks to the new scale, x1. - d3.transition(tick.exit()) - .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) - .style('opacity', 1e-6) - .remove(); - //============================================================ - // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ + // Main Chart Component(s) - dispatch.on('tooltipShow', function(e) { - e.key = data[0].title; - if (tooltips) showTooltip(e, that.parentNode); - }); + gEnter.select('.nv-background') + .append('rect'); - //============================================================ + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - }); + lines + //.x(function(d) { return d.x }) + .y(function(d) { return d.display.y }) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled })); - d3.timer.flush(); + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); + + //d3.transition(linesWrap).call(lines); + linesWrap.call(lines); + + + var indexLine = linesWrap.selectAll('.nv-indexLine') + .data([index]); + indexLine.enter().append('rect').attr('class', 'nv-indexLine') + .attr('width', 3) + .attr('x', -2) + .attr('fill', 'red') + .attr('fill-opacity', .5) + .call(indexDrag) + + indexLine + .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) + .attr('height', availableHeight) + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates) + .ticks( Math.min(data[0].values.length,availableWidth/70) ) + .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); + + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + + function updateZero() { + indexLine + .data([index]); + + chart.update(); + } + + g.select('.nv-background rect') + .on('click', function() { + index.x = d3.mouse(this)[0]; + index.i = Math.round(dx.invert(index.x)); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + lines.dispatch.on('elementClick', function(e) { + index.i = e.pointIndex; + index.x = dx(index.i); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + rescaleY = !d.disabled; + + state.rescaleY = rescaleY; + dispatch.stateChange(state); + + //selection.transition().call(chart); + selection.call(chart); + }); + + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + //selection.transition().call(chart); + selection.call(chart); + }); + +/* + // + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + + if (typeof e.index !== 'undefined') { + index.i = e.index; + index.x = dx(index.i); + + state.index = e.index; + + indexLine + .data([index]); + } + + + if (typeof e.rescaleY !== 'undefined') { + rescaleY = e.rescaleY; + } + + selection.call(chart); + }); + + //============================================================ + + }); return chart; } @@ -1651,11 +1942,12 @@ nv.models.bulletChart = function() { // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - bullet.dispatch.on('elementMouseover.tooltip', function(e) { + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; dispatch.tooltipShow(e); }); - bullet.dispatch.on('elementMouseout.tooltip', function(e) { + lines.dispatch.on('elementMouseout.tooltip', function(e) { dispatch.tooltipHide(e); }); @@ -1670,64 +1962,58 @@ nv.models.bulletChart = function() { // Expose Public Variables //------------------------------------------------------------ + // expose chart's sub-components chart.dispatch = dispatch; - chart.bullet = bullet; - - d3.rebind(chart, bullet, 'color'); + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; - // left, right, top, bottom - chart.orient = function(x) { - if (!arguments.length) return orient; - orient = x; - reverse = orient == 'right' || orient == 'bottom'; - return chart; - }; + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); - // ranges (bad, satisfactory, good) - chart.ranges = function(x) { - if (!arguments.length) return ranges; - ranges = x; + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; return chart; }; - // markers (previous, goal) - chart.markers = function(x) { - if (!arguments.length) return markers; - markers = x; + chart.width = function(_) { + if (!arguments.length) return width; + width = _; return chart; }; - // measures (actual, forecast) - chart.measures = function(x) { - if (!arguments.length) return measures; - measures = x; + chart.height = function(_) { + if (!arguments.length) return height; + height = _; return chart; }; - chart.width = function(x) { - if (!arguments.length) return width; - width = x; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); return chart; }; - chart.height = function(x) { - if (!arguments.length) return height; - height = x; - return chart; + chart.rescaleY = function(_) { + if (!arguments.length) return rescaleY; + rescaleY = _ + return rescaleY; }; - chart.margin = function(_) { - if (!arguments.length) return margin; - 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; + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; return chart; }; - chart.tickFormat = function(x) { - if (!arguments.length) return tickFormat; - tickFormat = x; + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; return chart; }; @@ -1743,6 +2029,12 @@ nv.models.bulletChart = function() { return chart; }; + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + chart.noData = function(_) { if (!arguments.length) return noData; noData = _; @@ -1752,50 +2044,75 @@ nv.models.bulletChart = function() { //============================================================ - return chart; -}; - - - -nv.models.cumulativeLineChart = function() { - //============================================================ - // Public Variables with Default Settings + // Functions //------------------------------------------------------------ - var lines = nv.models.line() + /* Normalize the data according to an index point. */ + function indexify(idx, data) { + return data.map(function(line, i) { + var v = lines.y()(line.values[idx], idx); + + //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue + if (v < -.95) { + //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically currect till it hits 100) + line.tempDisabled = true; + return line; + } + + line.tempDisabled = false; + + line.values = line.values.map(function(point, pointIndex) { + point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) }; + return point; + }) + + return line; + }) + } + + //============================================================ + + + return chart; +} + +nv.models.discreteBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var discretebar = nv.models.discreteBar() , xAxis = nv.models.axis() , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() ; - var margin = {top: 30, right: 30, bottom: 50, left: 60} - , color = nv.utils.defaultColor() + var margin = {top: 15, right: 10, bottom: 50, left: 60} , width = null , height = null - , showLegend = true + , color = nv.utils.getColor() + , staggerLabels = false , tooltips = true - , showControls = true - , rescaleY = true , tooltip = function(key, x, y, e, graph) { - return '

' + key + '

' + - '

' + y + ' at ' + x + '

' + return '

' + x + '

' + + '

' + y + '

' } - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , id = lines.id() - , state = { index: 0, rescaleY: rescaleY } - , noData = 'No Data Available.' - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , x + , y + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate') ; xAxis .orient('bottom') - .tickPadding(7) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) ; yAxis .orient('left') + .tickFormat(d3.format(',.1f')) ; //============================================================ @@ -1805,53 +2122,22 @@ nv.models.cumulativeLineChart = function() { // Private Variables //------------------------------------------------------------ - var dx = d3.scale.linear() - , index = {i: 0, x: 0} - ; - var showTooltip = function(e, offsetElement) { var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), top = e.pos[1] + ( offsetElement.offsetTop || 0), - x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), - y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)), content = tooltip(e.series.key, x, y, e, chart); - nv.tooltip.show([left, top], content, null, null, offsetElement); + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); }; -/* - //Moved to see if we can get better behavior to fix issue #315 - var indexDrag = d3.behavior.drag() - .on('dragstart', dragStart) - .on('drag', dragMove) - .on('dragend', dragEnd); - - function dragStart(d,i) { - d3.select(chart.container) - .style('cursor', 'ew-resize'); - } - - function dragMove(d,i) { - d.x += d3.event.dx; - d.i = Math.round(dx.invert(d.x)); - - d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)'); - chart.update(); - } - - function dragEnd(d,i) { - d3.select(chart.container) - .style('cursor', 'auto'); - chart.update(); - } -*/ - //============================================================ function chart(selection) { selection.each(function(data) { - var container = d3.select(this).classed('nv-chart-' + id, true), + var container = d3.select(this), that = this; var availableWidth = (width || parseInt(container.style('width')) || 960) @@ -1860,40 +2146,10 @@ nv.models.cumulativeLineChart = function() { - margin.top - margin.bottom; - chart.update = function() { chart(selection) }; + chart.update = function() { dispatch.beforeUpdate(); selection.transition().call(chart); }; chart.container = this; - - var indexDrag = d3.behavior.drag() - .on('dragstart', dragStart) - .on('drag', dragMove) - .on('dragend', dragEnd); - - - function dragStart(d,i) { - d3.select(chart.container) - .style('cursor', 'ew-resize'); - } - - function dragMove(d,i) { - index.x = d3.event.x; - index.i = Math.round(dx.invert(index.x)); - updateZero(); - } - - function dragEnd(d,i) { - d3.select(chart.container) - .style('cursor', 'auto'); - - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - } - - - - //------------------------------------------------------------ // Display No Data message if there's nothing to show. @@ -1921,184 +2177,83 @@ nv.models.cumulativeLineChart = function() { //------------------------------------------------------------ // Setup Scales - x = lines.xScale(); - y = lines.yScale(); - - - if (!rescaleY) { - var seriesDomains = data - .filter(function(series) { return !series.disabled }) - .map(function(series,i) { - var initialDomain = d3.extent(series.values, lines.y()); - - //account for series being disabled when losing 95% or more - if (initialDomain[0] < -.95) initialDomain[0] = -.95; - - return [ - (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), - (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) - ]; - }); - - var completeDomain = [ - d3.min(seriesDomains, function(d) { return d[0] }), - d3.max(seriesDomains, function(d) { return d[1] }) - ] - - lines.yDomain(completeDomain); - } else { - lines.yDomain(null); - } - - - dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length - .range([0, availableWidth]) - .clamp(true); + x = discretebar.xScale(); + y = discretebar.yScale(); //------------------------------------------------------------ - var data = indexify(index.i, data); - - //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); + var defsEnter = gEnter.append('defs'); var g = wrap.select('g'); gEnter.append('g').attr('class', 'nv-x nv-axis'); gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-background'); - gEnter.append('g').attr('class', 'nv-linesWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ //------------------------------------------------------------ - // Legend + // Main Chart Component(s) - if (showLegend) { - legend.width(availableWidth); + discretebar + .width(availableWidth) + .height(availableHeight); - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) - g.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } + d3.transition(barsWrap).call(discretebar); //------------------------------------------------------------ - //------------------------------------------------------------ - // Controls - if (showControls) { - var controlsData = [ - { key: 'Re-scale y-axis', disabled: !rescaleY } - ]; + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + discretebar.id()) + .append('rect'); + + g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') + .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) + .attr('height', 16) + .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); - controls.width(140).color(['#444', '#444', '#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } //------------------------------------------------------------ + // Setup Axes + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis').transition().duration(0) + .call(xAxis); - // Show error if series goes below 100% - var tempDisabled = data.filter(function(d) { return d.tempDisabled }); + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); - wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates - if (tempDisabled.length) { - wrap.append('text').attr('class', 'tempDisabled') - .attr('x', availableWidth / 2) - .attr('y', '-.71em') - .style('text-anchor', 'end') - .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + if (staggerLabels) { + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) } - - - //------------------------------------------------------------ - // Main Chart Component(s) - - gEnter.select('.nv-background') - .append('rect'); - - g.select('.nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - lines - //.x(function(d) { return d.x }) - .y(function(d) { return d.display.y }) - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled })); - - - - var linesWrap = g.select('.nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); - - //d3.transition(linesWrap).call(lines); - linesWrap.call(lines); - - - var indexLine = linesWrap.selectAll('.nv-indexLine') - .data([index]); - indexLine.enter().append('rect').attr('class', 'nv-indexLine') - .attr('width', 3) - .attr('x', -2) - .attr('fill', 'red') - .attr('fill-opacity', .5) - .call(indexDrag) - - indexLine - .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) - .attr('height', availableHeight) - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Setup Axes - - xAxis - .scale(x) - //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates) - .ticks( Math.min(data[0].values.length,availableWidth/70) ) - .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); - - - yAxis - .scale(y) - .ticks( availableHeight / 36 ) - .tickSize( -availableWidth, 0); + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); d3.transition(g.select('.nv-y.nv-axis')) .call(yAxis); @@ -2110,133 +2265,28 @@ nv.models.cumulativeLineChart = function() { // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ - - function updateZero() { - indexLine - .data([index]); - - chart.update(); - } - - g.select('.nv-background rect') - .on('click', function() { - index.x = d3.mouse(this)[0]; - index.i = Math.round(dx.invert(index.x)); - - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - - updateZero(); - }); - - lines.dispatch.on('elementClick', function(e) { - index.i = e.pointIndex; - index.x = dx(index.i); - - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - - updateZero(); - }); - - controls.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; - rescaleY = !d.disabled; - - state.rescaleY = rescaleY; - dispatch.stateChange(state); - - //selection.transition().call(chart); - selection.call(chart); - }); - - - legend.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; - - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - wrap.selectAll('.nv-series').classed('disabled', false); - return d; - }); - } - - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - - //selection.transition().call(chart); - selection.call(chart); - }); - -/* - // - legend.dispatch.on('legendMouseover', function(d, i) { - d.hover = true; - selection.transition().call(chart) - }); - - legend.dispatch.on('legendMouseout', function(d, i) { - d.hover = false; - selection.transition().call(chart) - }); -*/ - dispatch.on('tooltipShow', function(e) { if (tooltips) showTooltip(e, that.parentNode); }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - - if (typeof e.index !== 'undefined') { - index.i = e.index; - index.x = dx(index.i); - - state.index = e.index; - - indexLine - .data([index]); - } - - - if (typeof e.rescaleY !== 'undefined') { - rescaleY = e.rescaleY; - } - - selection.call(chart); - }); - //============================================================ + }); return chart; } - //============================================================ // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - lines.dispatch.on('elementMouseover.tooltip', function(e) { + discretebar.dispatch.on('elementMouseover.tooltip', function(e) { e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; dispatch.tooltipShow(e); }); - lines.dispatch.on('elementMouseout.tooltip', function(e) { + discretebar.dispatch.on('elementMouseout.tooltip', function(e) { dispatch.tooltipHide(e); }); @@ -2253,12 +2303,11 @@ nv.models.cumulativeLineChart = function() { // expose chart's sub-components chart.dispatch = dispatch; - chart.lines = lines; - chart.legend = legend; + chart.discretebar = discretebar; chart.xAxis = xAxis; chart.yAxis = yAxis; - d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat'); chart.margin = function(_) { if (!arguments.length) return margin; @@ -2284,25 +2333,13 @@ nv.models.cumulativeLineChart = function() { chart.color = function(_) { if (!arguments.length) return color; color = nv.utils.getColor(_); - legend.color(color); - return chart; - }; - - chart.rescaleY = function(_) { - if (!arguments.length) return rescaleY; - rescaleY = _ - return rescaleY; - }; - - chart.showControls = function(_) { - if (!arguments.length) return showControls; - showControls = _; + discretebar.color(color); return chart; }; - chart.showLegend = function(_) { - if (!arguments.length) return showLegend; - showLegend = _; + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; return chart; }; @@ -2318,12 +2355,6 @@ nv.models.cumulativeLineChart = function() { return chart; }; - chart.state = function(_) { - if (!arguments.length) return state; - state = _; - return chart; - }; - chart.noData = function(_) { if (!arguments.length) return noData; noData = _; @@ -2333,36 +2364,6 @@ nv.models.cumulativeLineChart = function() { //============================================================ - //============================================================ - // Functions - //------------------------------------------------------------ - - /* Normalize the data according to an index point. */ - function indexify(idx, data) { - return data.map(function(line, i) { - var v = lines.y()(line.values[idx], idx); - - //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue - if (v < -.95) { - //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically currect till it hits 100) - line.tempDisabled = true; - return line; - } - - line.tempDisabled = false; - - line.values = line.values.map(function(point, pointIndex) { - point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) }; - return point; - }) - - return line; - }) - } - - //============================================================ - - return chart; } //TODO: consider deprecating by adding necessary features to multiBar model @@ -2693,42 +2694,20 @@ nv.models.discreteBar = function() { return chart; } -nv.models.discreteBarChart = function() { +nv.models.distribution = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var discretebar = nv.models.discreteBar() - , 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() - , staggerLabels = false - , tooltips = true - , tooltip = function(key, x, y, e, graph) { - return '

' + x + '

' + - '

' + y + '

' - } - , x - , y - , noData = "No Data Available." - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') - ; - - xAxis - .orient('bottom') - .highlightZero(false) - .showMaxMin(false) - .tickFormat(function(d) { return d }) - ; - yAxis - .orient('left') - .tickFormat(d3.format(',.1f')) + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 400 //technically width or height depending on x or y.... + , size = 8 + , axis = 'x' // 'x' or 'y'... horizontal or vertical + , getData = function(d) { return d[axis] } // defaults d.x or d.y + , color = nv.utils.defaultColor() + , scale = d3.scale.linear() + , domain ; //============================================================ @@ -2738,63 +2717,22 @@ nv.models.discreteBarChart = function() { // Private Variables //------------------------------------------------------------ - var showTooltip = function(e, offsetElement) { - var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), - top = e.pos[1] + ( offsetElement.offsetTop || 0), - x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)), - y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)), - content = tooltip(e.series.key, x, y, e, chart); - - nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); - }; + var scale0; //============================================================ function chart(selection) { selection.each(function(data) { - var container = d3.select(this), - that = this; - - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - - - chart.update = function() { selection.transition().call(chart); }; - chart.container = this; - - - //------------------------------------------------------------ - // Display No Data message if there's nothing to show. - - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); - - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); - - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); - - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - //------------------------------------------------------------ + var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), + naxis = axis == 'x' ? 'y' : 'x', + container = d3.select(this); //------------------------------------------------------------ // Setup Scales - x = discretebar.xScale(); - y = discretebar.yScale(); + scale0 = scale0 || scale; //------------------------------------------------------------ @@ -2802,129 +2740,55 @@ nv.models.discreteBarChart = function() { //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); - var defsEnter = gEnter.append('defs'); + var wrap = container.selectAll('g.nv-distribution').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var gEnter = wrapEnter.append('g'); var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-barsWrap'); - - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Main Chart Component(s) - - discretebar - .width(availableWidth) - .height(availableHeight); - - - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })) - - d3.transition(barsWrap).call(discretebar); - - //------------------------------------------------------------ - - - - defsEnter.append('clipPath') - .attr('id', 'nv-x-label-clip-' + discretebar.id()) - .append('rect'); - - g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') - .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) - .attr('height', 16) - .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); - + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') //------------------------------------------------------------ - // Setup Axes - - xAxis - .scale(x) - .ticks( availableWidth / 100 ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); - //d3.transition(g.select('.nv-x.nv-axis')) - g.select('.nv-x.nv-axis').transition().duration(0) - .call(xAxis); - - - var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); - if (staggerLabels) { - xTicks - .selectAll('text') - .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) - } - - yAxis - .scale(y) - .ticks( availableHeight / 36 ) - .tickSize( -availableWidth, 0); - - d3.transition(g.select('.nv-y.nv-axis')) - .call(yAxis); - - //------------------------------------------------------------ + var distWrap = g.selectAll('g.nv-dist') + .data(function(d) { return d }, function(d) { return d.key }); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + distWrap.enter().append('g'); + distWrap + .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) + .style('stroke', function(d,i) { return color(d, i) }); - dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e, that.parentNode); - }); + var dist = distWrap.selectAll('line.nv-dist' + axis) + .data(function(d) { return d.values }) + dist.enter().append('line') + .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) + d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis)) + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + .style('stroke-opacity', 0) + .remove(); + dist + .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) + .attr(naxis + '1', 0) + .attr(naxis + '2', size); + d3.transition(dist) + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) - //============================================================ + scale0 = scale.copy(); }); return chart; } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - discretebar.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - discretebar.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); - }); - - //============================================================ - //============================================================ // Expose Public Variables //------------------------------------------------------------ - // expose chart's sub-components - chart.dispatch = dispatch; - chart.discretebar = discretebar; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - - d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat'); - chart.margin = function(_) { if (!arguments.length) return margin; margin.top = typeof _.top != 'undefined' ? _.top : margin.top; @@ -2940,40 +2804,33 @@ nv.models.discreteBarChart = function() { return chart; }; - chart.height = function(_) { - if (!arguments.length) return height; - height = _; - return chart; - }; - - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - discretebar.color(color); + chart.axis = function(_) { + if (!arguments.length) return axis; + axis = _; return chart; }; - chart.staggerLabels = function(_) { - if (!arguments.length) return staggerLabels; - staggerLabels = _; + chart.size = function(_) { + if (!arguments.length) return size; + size = _; return chart; }; - chart.tooltips = function(_) { - if (!arguments.length) return tooltips; - tooltips = _; + chart.getData = function(_) { + if (!arguments.length) return getData; + getData = d3.functor(_); return chart; }; - chart.tooltipContent = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; return chart; }; - chart.noData = function(_) { - if (!arguments.length) return noData; - noData = _; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); return chart; }; @@ -2982,46 +2839,65 @@ nv.models.discreteBarChart = function() { return chart; } - -nv.models.distribution = function() { +//TODO: consider deprecating and using multibar with single series for this +nv.models.historicalBar = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 400 //technically width or height depending on x or y.... - , size = 8 - , axis = 'x' // 'x' or 'y'... horizontal or vertical - , getData = function(d) { return d[axis] } // defaults d.x or d.y - , color = nv.utils.defaultColor() - , scale = d3.scale.linear() - , domain - ; - - //============================================================ - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var scale0; + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceX = [] + , forceY = [0] + , padData = false + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; //============================================================ function chart(selection) { selection.each(function(data) { - var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), - naxis = axis == 'x' ? 'y' : 'x', + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, container = d3.select(this); //------------------------------------------------------------ // Setup Scales - scale0 = scale0 || scale; + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )) + + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); //------------------------------------------------------------ @@ -3029,44 +2905,118 @@ nv.models.distribution = function() { //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-distribution').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var wrap = container.selectAll('g.nv-wrap.nv-bar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bar'); + var defsEnter = wrapEnter.append('defs'); var gEnter = wrapEnter.append('g'); var g = wrap.select('g'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + gEnter.append('g').attr('class', 'nv-bars'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ - var distWrap = g.selectAll('g.nv-dist') - .data(function(d) { return d }, function(d) { return d.key }); + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); - distWrap.enter().append('g'); - distWrap - .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) - .style('stroke', function(d,i) { return color(d, i) }); - var dist = distWrap.selectAll('line.nv-dist' + axis) - .data(function(d) { return d.values }) - dist.enter().append('line') - .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) - d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis)) - .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) - .style('stroke-opacity', 0) - .remove(); - dist - .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) - .attr(naxis + '1', 0) - .attr(naxis + '2', size); - d3.transition(dist) - .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - scale0 = scale.copy(); + + var bars = wrap.select('.nv-bars').selectAll('.nv-bar') + .data(function(d) { return d }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('x', 0 ) + .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + bars + .attr('fill', function(d,i) { return color(d, i); }) + .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) //TODO: better width calculations that don't assume always uniform data spacing;w + .attr('width', (availableWidth / data[0].values.length) * .9 ) + + + d3.transition(bars) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) + }) + .attr('height', function(d,i) { return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) }); + //.order(); // not sure if this makes any sense for this model }); @@ -3078,6 +3028,20 @@ nv.models.distribution = function() { // Expose Public Variables //------------------------------------------------------------ + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + chart.margin = function(_) { if (!arguments.length) return margin; margin.top = typeof _.top != 'undefined' ? _.top : margin.top; @@ -3093,27 +3057,57 @@ nv.models.distribution = function() { return chart; }; - chart.axis = function(_) { - if (!arguments.length) return axis; - axis = _; + chart.height = function(_) { + if (!arguments.length) return height; + height = _; return chart; }; - chart.size = function(_) { - if (!arguments.length) return size; - size = _; + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; return chart; }; - chart.getData = function(_) { - if (!arguments.length) return getData; - getData = d3.functor(_); + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; return chart; }; - chart.scale = function(_) { - if (!arguments.length) return scale; - scale = _; + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; return chart; }; @@ -3123,6 +3117,12 @@ nv.models.distribution = function() { return chart; }; + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + //============================================================ @@ -3638,32 +3638,42 @@ nv.models.legend = function() { return chart; } -nv.models.line = function() { +nv.models.lineChart = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var scatter = nv.models.scatter() + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() ; - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , color = nv.utils.defaultColor() // a function that returns a color - , getX = function(d) { return d.x } // accessor to get the x value from a data point - , getY = function(d) { return d.y } // accessor to get the y value from a data point - , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continous when it is not defined - , isArea = function(d) { return d.area } // decides if a line is an area or just a line - , clipEdge = false // if true, masks lines within x and y scale - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , interpolate = "linear" // controls the line interpolation +//set margin.right to 23 to fit dates on the x-axis within the chart + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

' + key + '

' + + '

' + y + ' at ' + x + '

' + } + , x + , y + , state = {} + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') ; - scatter - .size(16) // default size - .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') ; //============================================================ @@ -3673,182 +3683,255 @@ nv.models.line = function() { // Private Variables //------------------------------------------------------------ - var x0, y0 //used to store previous scales - ; + var showTooltip = function(e, offsetElement) { + + // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else + if (offsetElement) { + var svg = d3.select(offsetElement).select('svg'); + var viewBox = svg.attr('viewBox'); + if (viewBox) { + viewBox = viewBox.split(' '); + var ratio = parseInt(svg.style('width')) / viewBox[2]; + e.pos[0] = e.pos[0] * ratio; + e.pos[1] = e.pos[1] * ratio; + } + } + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; //============================================================ function chart(selection) { selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom, - container = d3.select(this); - - //------------------------------------------------------------ - // Setup Scales + var container = d3.select(this), + that = this; - x = scatter.xScale(); - y = scatter.yScale(); + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; - x0 = x0 || x; - y0 = y0 || y; - //------------------------------------------------------------ + chart.update = function() { chart(selection) }; + chart.container = this; //------------------------------------------------------------ - // Setup containers and skeleton of chart + // Display noData message if there's nothing to show. - var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g') + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); - gEnter.append('g').attr('class', 'nv-groups'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } //------------------------------------------------------------ + //------------------------------------------------------------ + // Setup Scales + x = lines.xScale(); + y = lines.yScale(); - scatter - .width(availableWidth) - .height(availableHeight) + //------------------------------------------------------------ - var scatterWrap = wrap.select('.nv-scatterWrap'); - //.datum(data); // Data automatically trickles down from the wrap - d3.transition(scatterWrap).call(scatter); + //------------------------------------------------------------ + // 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'); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + scatter.id()) - .append('rect'); + //------------------------------------------------------------ - wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); - scatterWrap - .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + //------------------------------------------------------------ + // Legend + if (showLegend) { + legend.width(availableWidth); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - d3.transition(groups.exit()) - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6) - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .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)}); - d3.transition(groups) - .style('stroke-opacity', 1) - .style('fill-opacity', .5); + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + //------------------------------------------------------------ + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - var areaPaths = groups.selectAll('path.nv-area') - .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area - areaPaths.enter().append('path') - .attr('class', 'nv-area') - .attr('d', function(d) { - return d3.svg.area() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return x0(getX(d,i)) }) - .y0(function(d,i) { return y0(getY(d,i)) }) - .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) - //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this - .apply(this, [d.values]) - }); - d3.transition(groups.exit().selectAll('path.nv-area')) - .attr('d', function(d) { - return d3.svg.area() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return x0(getX(d,i)) }) - .y0(function(d,i) { return y0(getY(d,i)) }) - .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) - //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this - .apply(this, [d.values]) - }); - d3.transition(areaPaths) - .attr('d', function(d) { - return d3.svg.area() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return x0(getX(d,i)) }) - .y0(function(d,i) { return y0(getY(d,i)) }) - .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) - //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this - .apply(this, [d.values]) - }); + //------------------------------------------------------------ + // Main Chart Component(s) + lines + .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 linePaths = groups.selectAll('path.nv-line') - .data(function(d) { return [d.values] }); - linePaths.enter().append('path') - .attr('class', 'nv-line') - .attr('d', - d3.svg.line() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return x0(getX(d,i)) }) - .y(function(d,i) { return y0(getY(d,i)) }) - ); - d3.transition(groups.exit().selectAll('path.nv-line')) - .attr('d', - d3.svg.line() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return x(getX(d,i)) }) - .y(function(d,i) { return y(getY(d,i)) }) - ); - d3.transition(linePaths) - .attr('d', - d3.svg.line() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return x(getX(d,i)) }) - .y(function(d,i) { return y(getY(d,i)) }) - ); + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + d3.transition(linesWrap).call(lines); - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); + //------------------------------------------------------------ - }); - return chart; - } + //------------------------------------------------------------ + // Setup Axes + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); - //============================================================ - // Expose Public Variables + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + +/* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - chart.dispatch = scatter.dispatch; - chart.scatter = scatter; + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); - d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData'); + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate'); chart.margin = function(_) { if (!arguments.length) return margin; @@ -3871,48 +3954,40 @@ nv.models.line = function() { return chart; }; - chart.x = function(_) { - if (!arguments.length) return getX; - getX = _; - scatter.x(_); - return chart; - }; - - chart.y = function(_) { - if (!arguments.length) return getY; - getY = _; - scatter.y(_); + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); return chart; }; - chart.clipEdge = function(_) { - if (!arguments.length) return clipEdge; - clipEdge = _; + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - scatter.color(color); + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; return chart; }; - chart.interpolate = function(_) { - if (!arguments.length) return interpolate; - interpolate = _; + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; return chart; }; - chart.defined = function(_) { - if (!arguments.length) return defined; - defined = _; + chart.state = function(_) { + if (!arguments.length) return state; + state = _; return chart; }; - chart.isArea = function(_) { - if (!arguments.length) return isArea; - isArea = d3.functor(_); + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; return chart; }; @@ -3922,42 +3997,32 @@ nv.models.line = function() { return chart; } -nv.models.lineChart = function() { +nv.models.line = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var lines = nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() + var scatter = nv.models.scatter() ; -//set margin.right to 23 to fit dates on the x-axis within the chart - var margin = {top: 30, right: 20, bottom: 50, left: 60} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = true - , tooltips = true - , tooltip = function(key, x, y, e, graph) { - return '

' + key + '

' + - '

' + y + ' at ' + x + '

' - } - , x - , y - , state = {} - , noData = 'No Data Available.' - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that returns a color + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continous when it is not defined + , isArea = function(d) { return d.area } // decides if a line is an area or just a line + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , interpolate = "linear" // controls the line interpolation ; - xAxis - .orient('bottom') - .tickPadding(7) - ; - yAxis - .orient('left') + scatter + .size(16) // default size + .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor ; //============================================================ @@ -3967,216 +4032,167 @@ nv.models.lineChart = function() { // Private Variables //------------------------------------------------------------ - var showTooltip = function(e, offsetElement) { - - // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else - if (offsetElement) { - var svg = d3.select(offsetElement).select('svg'); - var viewBox = svg.attr('viewBox'); - if (viewBox) { - viewBox = viewBox.split(' '); - var ratio = parseInt(svg.style('width')) / viewBox[2]; - e.pos[0] = e.pos[0] * ratio; - e.pos[1] = e.pos[1] * ratio; - } - } - - var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), - top = e.pos[1] + ( offsetElement.offsetTop || 0), - x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), - y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), - content = tooltip(e.series.key, x, y, e, chart); - - nv.tooltip.show([left, top], content, null, null, offsetElement); - }; + var x0, y0 //used to store previous scales + ; //============================================================ function chart(selection) { selection.each(function(data) { - var container = d3.select(this), - that = this; + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; + //------------------------------------------------------------ + // Setup Scales + x = scatter.xScale(); + y = scatter.yScale(); - chart.update = function() { chart(selection) }; - chart.container = this; + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ //------------------------------------------------------------ - // Display noData message if there's nothing to show. + // Setup containers and skeleton of chart - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); + var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); - - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Setup Scales - - x = lines.xScale(); - y = lines.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'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-linesWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Legend - - if (showLegend) { - legend.width(availableWidth); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } - - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - //------------------------------------------------------------ - // Main Chart Component(s) - lines + scatter .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 linesWrap = g.select('.nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled })) - d3.transition(linesWrap).call(lines); + var scatterWrap = wrap.select('.nv-scatterWrap'); + //.datum(data); // Data automatically trickles down from the wrap - //------------------------------------------------------------ + d3.transition(scatterWrap).call(scatter); - //------------------------------------------------------------ - // Setup Axes - xAxis - .scale(x) - .ticks( availableWidth / 100 ) - .tickSize(-availableHeight, 0); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + scatter.id()) + .append('rect'); - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - d3.transition(g.select('.nv-x.nv-axis')) - .call(xAxis); + wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); - yAxis - .scale(y) - .ticks( availableHeight / 36 ) - .tickSize( -availableWidth, 0); - d3.transition(g.select('.nv-y.nv-axis')) - .call(yAxis); - //------------------------------------------------------------ + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .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)}); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - legend.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - wrap.selectAll('.nv-series').classed('disabled', false); - return d; + var areaPaths = groups.selectAll('path.nv-area') + .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area + areaPaths.enter().append('path') + .attr('class', 'nv-area') + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + d3.transition(groups.exit().selectAll('path.nv-area')) + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + d3.transition(areaPaths) + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) }); - } - - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - - selection.transition().call(chart); - }); - -/* - legend.dispatch.on('legendMouseover', function(d, i) { - d.hover = true; - selection.transition().call(chart) - }); - - legend.dispatch.on('legendMouseout', function(d, i) { - d.hover = false; - selection.transition().call(chart) - }); -*/ - - dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e, that.parentNode); - }); - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); + var linePaths = groups.selectAll('path.nv-line') + .data(function(d) { return [d.values] }); + linePaths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y(function(d,i) { return y0(getY(d,i)) }) + ); + d3.transition(groups.exit().selectAll('path.nv-line')) + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + d3.transition(linePaths) + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); - state.disabled = e.disabled; - } - selection.call(chart); - }); - //============================================================ + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); }); @@ -4184,38 +4200,14 @@ nv.models.lineChart = function() { } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - lines.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - lines.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); - }); - - //============================================================ - - //============================================================ // Expose Public Variables //------------------------------------------------------------ - // expose chart's sub-components - chart.dispatch = dispatch; - chart.lines = lines; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; + chart.dispatch = scatter.dispatch; + chart.scatter = scatter; - d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate'); + d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData'); chart.margin = function(_) { if (!arguments.length) return margin; @@ -4238,40 +4230,48 @@ nv.models.lineChart = function() { return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - legend.color(color); + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + scatter.x(_); return chart; }; - chart.showLegend = function(_) { - if (!arguments.length) return showLegend; - showLegend = _; + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + scatter.y(_); return chart; }; - chart.tooltips = function(_) { - if (!arguments.length) return tooltips; - tooltips = _; + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; return chart; }; - chart.tooltipContent = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); return chart; }; - chart.state = function(_) { - if (!arguments.length) return state; - state = _; + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; return chart; }; - chart.noData = function(_) { - if (!arguments.length) return noData; - noData = _; + chart.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return chart; + }; + + chart.isArea = function(_) { + if (!arguments.length) return isArea; + isArea = d3.functor(_); return chart; }; @@ -4703,7 +4703,7 @@ nv.models.linePlusBarChart = function() { return chart; } -nv.models.lineWithFocusChart = function() { +nv.models.linePlusBarWithFocusChart = function() { //============================================================ // Public Variables with Default Settings @@ -4711,31 +4711,40 @@ nv.models.lineWithFocusChart = function() { var lines = nv.models.line() , lines2 = nv.models.line() + , bars = nv.models.historicalBar() + , bars2 = nv.models.historicalBar() , xAxis = nv.models.axis() - , yAxis = nv.models.axis() , x2Axis = nv.models.axis() + , y1Axis = nv.models.axis() , y2Axis = nv.models.axis() + , y3Axis = nv.models.axis() + , y4Axis = nv.models.axis() , legend = nv.models.legend() , brush = d3.svg.brush() ; var margin = {top: 30, right: 30, bottom: 30, left: 60} , margin2 = {top: 0, right: 30, bottom: 20, left: 60} - , color = nv.utils.defaultColor() , width = null , height = null , height2 = 100 - , x - , y - , x2 - , y2 + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() , showLegend = true + , extent , brushExtent = null , tooltips = true , tooltip = function(key, x, y, e, graph) { return '

' + key + '

' + - '

' + y + ' at ' + x + '

' + '

' + y + ' at ' + x + '

'; } + , x + , x2 + , y1 + , y2 + , y3 + , y4 , noData = "No Data Available." , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') ; @@ -4750,16 +4759,23 @@ nv.models.lineWithFocusChart = function() { .orient('bottom') .tickPadding(5) ; - yAxis + y1Axis .orient('left') ; + y2Axis + .orient('right') + ; x2Axis .orient('bottom') .tickPadding(5) ; - y2Axis + y3Axis .orient('left') ; + y4Axis + .orient('right') + ; + //============================================================ @@ -4768,16 +4784,20 @@ nv.models.lineWithFocusChart = function() { //------------------------------------------------------------ var showTooltip = function(e, offsetElement) { + if (extent) { + e.pointIndex += Math.ceil(extent[0]); + } var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), top = e.pos[1] + ( offsetElement.offsetTop || 0), x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), - y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)), content = tooltip(e.series.key, x, y, e, chart); - nv.tooltip.show([left, top], content, null, null, offsetElement); + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); }; - //============================================================ + //------------------------------------------------------------ + function chart(selection) { @@ -4822,10 +4842,37 @@ nv.models.lineWithFocusChart = function() { //------------------------------------------------------------ // Setup Scales - x = lines.xScale(); - y = lines.yScale(); - x2 = lines2.xScale(); - y2 = lines2.yScale(); + 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(); + x2 = x2Axis.scale(); + y1 = bars.yScale(); + y2 = lines.yScale(); + y3 = bars2.yScale(); + y4 = lines2.yScale(); + + var series1 = data + .filter(function(d) { return !d.disabled && d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + var series2 = data + .filter(function(d) { return !d.disabled && !d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + x .range([0, availableWidth]); + + x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + //------------------------------------------------------------ @@ -4833,24 +4880,29 @@ nv.models.lineWithFocusChart = function() { //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); + var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); var g = wrap.select('g'); gEnter.append('g').attr('class', 'nv-legendWrap'); - + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); focusEnter.append('g').attr('class', 'nv-x nv-axis'); - focusEnter.append('g').attr('class', 'nv-y nv-axis'); + focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); + focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); + focusEnter.append('g').attr('class', 'nv-barsWrap'); focusEnter.append('g').attr('class', 'nv-linesWrap'); var contextEnter = gEnter.append('g').attr('class', 'nv-context'); contextEnter.append('g').attr('class', 'nv-x nv-axis'); - contextEnter.append('g').attr('class', 'nv-y nv-axis'); + contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); + contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); + contextEnter.append('g').attr('class', 'nv-barsWrap'); contextEnter.append('g').attr('class', 'nv-linesWrap'); contextEnter.append('g').attr('class', 'nv-brushBackground'); contextEnter.append('g').attr('class', 'nv-x nv-brush'); + //------------------------------------------------------------ @@ -4858,11 +4910,15 @@ nv.models.lineWithFocusChart = function() { // Legend if (showLegend) { - legend.width(availableWidth); + legend.width( availableWidth / 2 ); g.select('.nv-legendWrap') - .datum(data) - .call(legend); + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)'); + return series; + })) + .call(legend); if ( margin.top != legend.height()) { margin.top = legend.height(); @@ -4871,7 +4927,7 @@ nv.models.lineWithFocusChart = function() { } g.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); } //------------------------------------------------------------ @@ -4881,71 +4937,36 @@ nv.models.lineWithFocusChart = function() { //------------------------------------------------------------ - // Main Chart Component(s) + // Context Components - lines + bars2 .width(availableWidth) - .height(availableHeight1) - .color( - data - .map(function(d,i) { - return d.color || color(d, i); - }) - .filter(function(d,i) { - return !data[i].disabled; - }) - ); + .height(availableHeight2) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); 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; - }) - ); - + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var bars2Wrap = g.select('.nv-context .nv-barsWrap') + .datum(dataBars.length ? dataBars : [{values:[]}]); + + var lines2Wrap = g.select('.nv-context .nv-linesWrap') + .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]); + g.select('.nv-context') .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') - var contextLinesWrap = g.select('.nv-context .nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled })) - - d3.transition(contextLinesWrap).call(lines2); - - //------------------------------------------------------------ - - - /* - var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled })) - - d3.transition(focusLinesWrap).call(lines); - */ - + d3.transition(bars2Wrap).call(bars2); + d3.transition(lines2Wrap).call(lines2); //------------------------------------------------------------ - // Setup Main (Focus) Axes - - xAxis - .scale(x) - .ticks( availableWidth / 100 ) - .tickSize(-availableHeight1, 0); - - yAxis - .scale(y) - .ticks( availableHeight1 / 36 ) - .tickSize( -availableWidth, 0); - g.select('.nv-focus .nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight1 + ')'); - - //------------------------------------------------------------ //------------------------------------------------------------ @@ -4975,51 +4996,60 @@ nv.models.lineWithFocusChart = function() { .attr('y', 0) .attr('height', availableHeight2); - gBrush = g.select('.nv-x.nv-brush') + var gBrush = g.select('.nv-x.nv-brush') .call(brush); gBrush.selectAll('rect') //.attr('y', -5) .attr('height', availableHeight2); gBrush.selectAll('.resize').append('path').attr('d', resizePath); - onBrush(); - //------------------------------------------------------------ - //------------------------------------------------------------ // Setup Secondary (Context) Axes x2Axis - .scale(x2) .ticks( availableWidth / 100 ) .tickSize(-availableHeight2, 0); g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + .attr('transform', 'translate(0,' + y3.range()[0] + ')'); d3.transition(g.select('.nv-context .nv-x.nv-axis')) .call(x2Axis); - y2Axis - .scale(y2) + y3Axis + .scale(y3) .ticks( availableHeight2 / 36 ) .tickSize( -availableWidth, 0); - d3.transition(g.select('.nv-context .nv-y.nv-axis')) - .call(y2Axis); + g.select('.nv-context .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0) + .attr('transform', 'translate(0,' + x2.range()[0] + ')'); + + d3.transition(g.select('.nv-context .nv-y1.nv-axis')) + .call(y3Axis); + + + y4Axis + .scale(y4) + .ticks( availableHeight2 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + g.select('.nv-context .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x2.range()[1] + ',0)'); + d3.transition(g.select('.nv-context .nv-y2.nv-axis')) + .call(y4Axis); + //------------------------------------------------------------ - //============================================================ // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ - legend.dispatch.on('legendClick', function(d,i) { + legend.dispatch.on('legendClick', function(d,i) { d.disabled = !d.disabled; if (!data.filter(function(d) { return !d.disabled }).length) { @@ -5030,7 +5060,7 @@ nv.models.lineWithFocusChart = function() { }); } - selection.transition().call(chart); + selection.call(chart); }); dispatch.on('tooltipShow', function(e) { @@ -5066,8 +5096,8 @@ nv.models.lineWithFocusChart = function() { brushBG .data([brush.empty() ? x2.domain() : brushExtent]) .each(function(d,i) { - var leftWidth = x2(d[0]) - x.range()[0], - rightWidth = x.range()[1] - x2(d[1]); + var leftWidth = x2(d[0]) - x2.range()[0], + rightWidth = x2.range()[1] - x2(d[1]); d3.select(this).select('.left') .attr('width', leftWidth < 0 ? 0 : leftWidth); @@ -5085,14 +5115,42 @@ nv.models.lineWithFocusChart = function() { dispatch.brush({extent: extent, brush: brush}); - updateBrushBG(); - // Update Main (Focus) + + //------------------------------------------------------------ + // Prepare Main (Focus) Bars and Lines + + bars + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + + lines + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') + .datum(!dataBars.length ? [{values:[]}] : + dataBars + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; + }) + } + }) + ); + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') - .datum( - data - .filter(function(d) { return !d.disabled }) + .datum(dataLines[0].disabled ? [{values:[]}] : + dataLines .map(function(d,i) { return { key: d.key, @@ -5101,19 +5159,75 @@ nv.models.lineWithFocusChart = function() { }) } }) - ); + ); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Update Main (Focus) X Axis + + if (dataBars.length) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight1, 0); + + xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); + + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Update Main (Focus) Bars and Lines + + d3.transition(focusBarsWrap).call(bars); d3.transition(focusLinesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup and Update Main (Focus) Y Axes + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); - // Update Main (Focus) Axes - d3.transition(g.select('.nv-focus .nv-x.nv-axis')) - .call(xAxis); - d3.transition(g.select('.nv-focus .nv-y.nv-axis')) - .call(yAxis); + y1Axis + .scale(y1) + .ticks( availableHeight1 / 36 ) + .tickSize(-availableWidth, 0); + + g.select('.nv-focus .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0); + + + y2Axis + .scale(y2) + .ticks( availableHeight1 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-focus .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + d3.transition(g.select('.nv-focus .nv-y1.nv-axis')) + .call(y1Axis); + d3.transition(g.select('.nv-focus .nv-y2.nv-axis')) + .call(y2Axis); } //============================================================ + onBrush(); }); @@ -5134,10 +5248,19 @@ nv.models.lineWithFocusChart = function() { dispatch.tooltipHide(e); }); - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); - }); - + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + //============================================================ @@ -5150,24 +5273,32 @@ nv.models.lineWithFocusChart = function() { chart.legend = legend; chart.lines = lines; chart.lines2 = lines2; + chart.bars = bars; + chart.bars2 = bars2; chart.xAxis = xAxis; - chart.yAxis = yAxis; chart.x2Axis = x2Axis; + chart.y1Axis = y1Axis; chart.y2Axis = y2Axis; + chart.y3Axis = y3Axis; + chart.y4Axis = y4Axis; - d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate'); + //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc. + //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); chart.x = function(_) { - if (!arguments.length) return lines.x; + if (!arguments.length) return getX; + getX = _; lines.x(_); - lines2.x(_); + bars.x(_); return chart; }; chart.y = function(_) { - if (!arguments.length) return lines.y; + if (!arguments.length) return getY; + getY = _; lines.y(_); - lines2.y(_); + bars.y(_); return chart; }; @@ -5180,12 +5311,6 @@ nv.models.lineWithFocusChart = function() { return chart; }; - chart.margin2 = function(_) { - if (!arguments.length) return margin2; - margin2 = _; - return chart; - }; - chart.width = function(_) { if (!arguments.length) return width; width = _; @@ -5198,15 +5323,9 @@ nv.models.lineWithFocusChart = function() { return chart; }; - chart.height2 = function(_) { - if (!arguments.length) return height2; - height2 = _; - return chart; - }; - chart.color = function(_) { if (!arguments.length) return color; - color =nv.utils.getColor(_); + color = nv.utils.getColor(_); legend.color(color); return chart; }; @@ -5229,328 +5348,292 @@ nv.models.lineWithFocusChart = function() { return chart; }; - chart.interpolate = function(_) { - if (!arguments.length) return lines.interpolate(); - lines.interpolate(_); - lines2.interpolate(_); - return chart; - }; - chart.noData = function(_) { if (!arguments.length) return noData; noData = _; return chart; }; - // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below - chart.xTickFormat = function(_) { - if (!arguments.length) return xAxis.tickFormat(); - xAxis.tickFormat(_); - x2Axis.tickFormat(_); - return chart; - }; - - chart.yTickFormat = function(_) { - if (!arguments.length) return yAxis.tickFormat(); - yAxis.tickFormat(_); - y2Axis.tickFormat(_); - return chart; - }; - //============================================================ return chart; } -nv.models.multiBar = function() { +nv.models.lineChart = function() { + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + color = nv.utils.defaultColor(), + width = null, + height = null, + showLegend = true, + showControls = true, + fisheye = 0, + pauseFisheye = false, + tooltips = true, + tooltip = function(key, x, y, e, graph) { + return '

' + key + '

' + + '

' + y + ' at ' + x + '

' + }, + noData = "No Data Available." + ; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove - , clipEdge = true - , stacked = false - , color = nv.utils.defaultColor() - , delay = 1200 - , xDomain - , yDomain - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') - ; + var x = d3.fisheye.scale(d3.scale.linear).distortion(0); - //============================================================ + var lines = nv.models.line().xScale(x), + //x = lines.xScale(), + y = lines.yScale(), + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis = nv.models.axis().scale(y).orient('left'), + legend = nv.models.legend().height(30), + controls = nv.models.legend().height(30), + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); - //============================================================ - // Private Variables - //------------------------------------------------------------ + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); - var x0, y0 //used to store previous scales - ; + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; - //============================================================ + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; function chart(selection) { selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom, - container = d3.select(this); + var container = d3.select(this), + that = this; - if (stacked) - data = d3.layout.stack() - .offset('zero') - .values(function(d){ return d.values }) - .y(getY) - (data); + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; - //add series index to each data point for reference - data = data.map(function(series, i) { - series.values = series.values.map(function(point) { - point.series = i; - return point; - }); - return series; - }); + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + container.append('text') + .attr('class', 'nvd3 nv-noData') + .attr('x', availableWidth / 2) + .attr('y', availableHeight / 2) + .attr('dy', '-.7em') + .style('text-anchor', 'middle') + .text(noData); + return chart; + } else { + container.select('.nv-noData').remove(); + } //------------------------------------------------------------ - // Setup Scales - // remap and flatten the data for use in calculating the scales' domains - var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate - data.map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i), y0: d.y0 } - }) - }); - x .domain(d3.merge(seriesData).map(function(d) { return d.x })) - .rangeBands([0, availableWidth], .1); - y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) - .range([availableHeight, 0]); + 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'); - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); + gEnter.append('rect') + .attr('class', 'nvd3 nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) - : y.domain([-1,1]); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); - x0 = x0 || x; - y0 = y0 || y; - //------------------------------------------------------------ + var g = wrap.select('g'); - //------------------------------------------------------------ - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g') - gEnter.append('g').attr('class', 'nv-groups'); + if (showLegend) { + legend.width(availableWidth); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - //------------------------------------------------------------ + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + lines + .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 groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - d3.transition(groups.exit()) - //.style('stroke-opacity', 1e-6) - //.style('fill-opacity', 1e-6) - .selectAll('rect.nv-bar') - .delay(function(d,i) { return i * delay/ data[0].values.length }) - .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) - .attr('height', 0) - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .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) }); - d3.transition(groups) - .style('stroke-opacity', 1) - .style('fill-opacity', .75); + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - var bars = groups.selectAll('rect.nv-bar') - .data(function(d) { return d.values }); - bars.exit().remove(); + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + d3.transition(linesWrap).call(lines); - var barsEnter = bars.enter().append('rect') - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - .attr('x', function(d,i,j) { - return stacked ? 0 : (j * x.rangeBand() / data.length ) - }) - .attr('y', function(d) { return y0(stacked ? d.y0 : 0) }) - .attr('height', 0) - .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); - bars - .style('fill', function(d,i,j){ return color(d, j, i); }) - .style('stroke', function(d,i,j){ return color(d, j, i); }) - .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - value: getY(d,i), - point: d, - series: data[d.series], - pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - value: getY(d,i), - point: d, - series: data[d.series], - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - value: getY(d,i), - point: d, - series: data[d.series], - pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - value: getY(d,i), - point: d, - series: data[d.series], - pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - d3.event.stopPropagation(); + + + xAxis + //.scale(x) + .ticks( availableWidth / 100 ) + .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); + + + yAxis + //.scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye; }); + //g.select('.point-paths').on('mousemove', updateFisheye); + + + function updateFisheye() { + if (pauseFisheye) { + //g.select('.background') .style('pointer-events', 'none'); + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-background') .style('pointer-events', 'all'); + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + linesWrap.call(lines); + g.select('.nv-x.nv-axis').call(xAxis); + x.distortion(fisheye).focus(mouse[0]); + } + + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + //scatter.interactive(d.disabled); + //tooltips = d.disabled; + + if (d.disabled) { + x.distortion(fisheye).focus(0); + + linesWrap.call(lines); + g.select('.nv-x.nv-axis').call(xAxis); + } else { + pauseFisheye = false; + } + + selection.transition().call(chart); + }); + + + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; }); - bars - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) - if (stacked) - d3.transition(bars) - .delay(function(d,i) { return i * delay / data[0].values.length }) - .attr('y', function(d,i) { - return y(getY(d,i) + (stacked ? d.y0 : 0)); - }) - .attr('height', function(d,i) { - return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1); - }) - .each('end', function() { - d3.transition(d3.select(this)) - .attr('x', function(d,i) { - return stacked ? 0 : (d.series * x.rangeBand() / data.length ) - }) - .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); - }) - else - d3.transition(bars) - .delay(function(d,i) { return i * delay/ data[0].values.length }) - .attr('x', function(d,i) { - return d.series * x.rangeBand() / data.length - }) - .attr('width', x.rangeBand() / data.length) - .each('end', function() { - d3.transition(d3.select(this)) - .attr('y', function(d,i) { - return getY(d,i) < 0 ? - y(0) : - y(0) - y(getY(d,i)) < 1 ? - y(0) - 1 : - y(getY(d,i)) - }) - .attr('height', function(d,i) { - return Math.max(Math.abs(y(getY(d,i)) - y(0)),1); - }); - }) + } + selection.transition().call(chart); + }); - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); +/* + // + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + if (tooltips) dispatch.on('tooltipShow', function(e) { showTooltip(e, that.parentNode) } ); // TODO: maybe merge with above? + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + if (tooltips) dispatch.on('tooltipHide', nv.tooltip.cleanup); }); + + //TODO: decide if this is a good idea, and if it should be in all models + chart.update = function() { chart(selection) }; + chart.container = this; // I need a reference to the container in order to have outside code check if the chart is visible or not + + return chart; } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - chart.dispatch = dispatch; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; - chart.x = function(_) { - if (!arguments.length) return getX; - getX = _; - return chart; - }; + d3.rebind(chart, lines, 'defined', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate'); - chart.y = function(_) { - if (!arguments.length) return getY; - getY = _; - return chart; - }; chart.margin = function(_) { if (!arguments.length) return margin; - 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; + margin = _; return chart; }; @@ -5566,51 +5649,214 @@ nv.models.multiBar = function() { return chart; }; - chart.xScale = function(_) { - if (!arguments.length) return x; - x = _; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); return chart; }; - chart.yScale = function(_) { - if (!arguments.length) return y; - y = _; + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; return chart; }; - chart.xDomain = function(_) { - if (!arguments.length) return xDomain; - xDomain = _; + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; return chart; }; - chart.yDomain = function(_) { - if (!arguments.length) return yDomain; - yDomain = _; + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; return chart; }; - chart.forceY = function(_) { - if (!arguments.length) return forceY; - forceY = _; + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; return chart; }; - chart.stacked = function(_) { - if (!arguments.length) return stacked; - stacked = _; - return chart; - }; - chart.clipEdge = function(_) { - if (!arguments.length) return clipEdge; - clipEdge = _; - return chart; - }; + return chart; +} - chart.color = function(_) { +nv.models.line = function() { + //Default Settings + var margin = {top: 0, right: 0, bottom: 0, left: 0}, + width = 960, + height = 500, + color = nv.utils.defaultColor(), // function that returns colors + id = Math.floor(Math.random() * 10000), //Create semi-unique ID incase user doesn't select one + getX = function(d) { return d.x }, // accessor to get the x value from a data point + getY = function(d) { return d.y }, // accessor to get the y value from a data point + clipEdge = false, // if true, masks lines within x and y scale + interpolate = "linear"; // controls the line interpolation + + + var scatter = nv.models.scatter() + .id(id) + .size(16) // default size + .sizeDomain([16,256]), //set to speed up calculation, needs to be unset if there is a cstom size accessor + //x = scatter.xScale(), + //y = scatter.yScale(), + x, y, + x0, y0, timeoutID; + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + //get the scales inscase scatter scale was set manually + x = x || scatter.xScale(); + y = y || scatter.yScale(); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y; + + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + wrapEnter.append('g').attr('class', 'nv-scatterWrap'); + var scatterWrap = wrap.select('.nv-scatterWrap').datum(data); + + gEnter.append('g').attr('class', 'nv-groups'); + + + scatter + .width(availableWidth) + .height(availableHeight) + + d3.transition(scatterWrap).call(scatter); + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .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) }) + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .5) + + + var paths = groups.selectAll('path') + .data(function(d, i) { return [d.values] }); + paths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', d3.svg.line() + .interpolate(interpolate) + .x(function(d,i) { return x0(getX(d,i)) }) + .y(function(d,i) { return y0(getY(d,i)) }) + ); + d3.transition(groups.exit().selectAll('path')) + .attr('d', d3.svg.line() + .interpolate(interpolate) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ) + .remove(); // redundant? line is already being removed + d3.transition(paths) + .attr('d', d3.svg.line() + .interpolate(interpolate) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + //store old scales for use in transitions on update, to animate from old to new positions + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + chart.dispatch = scatter.dispatch; + + d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + scatter.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + scatter.y(_); + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { if (!arguments.length) return color; color = nv.utils.getColor(_); + scatter.color(color); return chart; }; @@ -5620,66 +5866,78 @@ nv.models.multiBar = function() { return chart; }; - chart.delay = function(_) { - if (!arguments.length) return delay; - delay = _; + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; return chart; }; - //============================================================ - + chart.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return chart; + }; return chart; } -nv.models.multiBarChart = function() { +nv.models.lineWithFocusChart = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var multibar = nv.models.multiBar() + var lines = nv.models.line() + , lines2 = nv.models.line() , xAxis = nv.models.axis() , yAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y2Axis = nv.models.axis() , legend = nv.models.legend() - , controls = nv.models.legend() + , brush = d3.svg.brush() ; - var margin = {top: 30, right: 20, bottom: 30, left: 60} + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , color = nv.utils.defaultColor() , width = null , height = null - , color = nv.utils.defaultColor() - , showControls = true + , height2 = 100 + , x + , y + , x2 + , y2 , showLegend = true - , reduceXTicks = true // if false a tick will show for every data point - , rotateLabels = 0 + , brushExtent = null , tooltips = true , tooltip = function(key, x, y, e, graph) { return '

' + key + '

' + - '

' + y + ' on ' + x + '

' + '

' + y + ' at ' + x + '

' } - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , state = { stacked: false } , noData = "No Data Available." - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') ; - multibar - .stacked(false) + lines + .clipEdge(true) + ; + lines2 + .interactive(false) ; xAxis .orient('bottom') - .tickPadding(7) - .highlightZero(false) - .showMaxMin(false) - .tickFormat(function(d) { return d }) + .tickPadding(5) ; yAxis .orient('left') - .tickFormat(d3.format(',.1f')) ; - + x2Axis + .orient('bottom') + .tickPadding(5) + ; + y2Axis + .orient('left') + ; //============================================================ @@ -5690,11 +5948,11 @@ nv.models.multiBarChart = function() { var showTooltip = function(e, offsetElement) { var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), top = e.pos[1] + ( offsetElement.offsetTop || 0), - x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), - y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), content = tooltip(e.series.key, x, y, e, chart); - nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + nv.tooltip.show([left, top], content, null, null, offsetElement); }; //============================================================ @@ -5707,15 +5965,16 @@ nv.models.multiBarChart = function() { var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; - chart.update = function() { selection.transition().call(chart) }; + chart.update = function() { chart(selection) }; chart.container = this; //------------------------------------------------------------ - // Display noData message if there's nothing to show. + // Display No Data message if there's nothing to show. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { var noDataText = container.selectAll('.nv-noData').data([noData]); @@ -5727,7 +5986,7 @@ nv.models.multiBarChart = function() { noDataText .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) + .attr('y', margin.top + availableHeight1 / 2) .text(function(d) { return d }); return chart; @@ -5741,8 +6000,10 @@ nv.models.multiBarChart = function() { //------------------------------------------------------------ // Setup Scales - x = multibar.xScale(); - y = multibar.yScale(); + x = lines.xScale(); + y = lines.yScale(); + x2 = lines2.xScale(); + y2 = lines2.yScale(); //------------------------------------------------------------ @@ -5750,15 +6011,23 @@ nv.models.multiBarChart = function() { //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-barsWrap'); gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + 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'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + 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'); //------------------------------------------------------------ @@ -5767,7 +6036,7 @@ nv.models.multiBarChart = function() { // Legend if (showLegend) { - legend.width(availableWidth / 2); + legend.width(availableWidth); g.select('.nv-legendWrap') .datum(data) @@ -5775,104 +6044,153 @@ nv.models.multiBarChart = function() { if ( margin.top != legend.height()) { margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2; } g.select('.nv-legendWrap') - .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + .attr('transform', 'translate(0,' + (-margin.top) +')') } //------------------------------------------------------------ - //------------------------------------------------------------ - // Controls - - if (showControls) { - var controlsData = [ - { key: 'Grouped', disabled: multibar.stacked() }, - { key: 'Stacked', disabled: !multibar.stacked() } - ]; - - controls.width(180).color(['#444', '#444', '#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } - - //------------------------------------------------------------ - - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ // Main Chart Component(s) - multibar + lines .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })) + .height(availableHeight1) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + 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) + ')') - var barsWrap = g.select('.nv-barsWrap') + var contextLinesWrap = g.select('.nv-context .nv-linesWrap') .datum(data.filter(function(d) { return !d.disabled })) - d3.transition(barsWrap).call(multibar); + d3.transition(contextLinesWrap).call(lines2); //------------------------------------------------------------ + /* + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(focusLinesWrap).call(lines); + */ + + //------------------------------------------------------------ - // Setup Axes + // Setup Main (Focus) Axes xAxis .scale(x) .ticks( availableWidth / 100 ) - .tickSize(-availableHeight, 0); + .tickSize(-availableHeight1, 0); - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - d3.transition(g.select('.nv-x.nv-axis')) - .call(xAxis); + yAxis + .scale(y) + .ticks( availableHeight1 / 36 ) + .tickSize( -availableWidth, 0); - var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight1 + ')'); - xTicks - .selectAll('line, text') - .style('opacity', 1) + //------------------------------------------------------------ - if (reduceXTicks) - xTicks - .filter(function(d,i) { - return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; - }) - .selectAll('text, line') - .style('opacity', 0); - if(rotateLabels) - xTicks - .selectAll('text') - .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' }) - .attr('text-transform', rotateLabels > 0 ? 'start' : 'end'); + //------------------------------------------------------------ + // Setup Brush - g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') - .style('opacity', 1); + brush + .x(x2) + .on('brush', onBrush); - yAxis - .scale(y) - .ticks( availableHeight / 36 ) - .tickSize( -availableWidth, 0); + 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); + + gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + onBrush(); + + //------------------------------------------------------------ - d3.transition(g.select('.nv-y.nv-axis')) - .call(yAxis); //------------------------------------------------------------ + // Setup Secondary (Context) Axes + + x2Axis + .scale(x2) + .ticks( availableWidth / 100 ) + .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); + + + y2Axis + .scale(y2) + .ticks( availableHeight2 / 36 ) + .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] + ')'); + //------------------------------------------------------------ //============================================================ @@ -5890,98 +6208,146 @@ nv.models.multiBarChart = function() { }); } - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - - selection.transition().call(chart); - }); - - controls.dispatch.on('legendClick', function(d,i) { - if (!d.disabled) return; - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; - }); - d.disabled = false; - - switch (d.key) { - case 'Grouped': - multibar.stacked(false); - break; - case 'Stacked': - multibar.stacked(true); - break; - } - - state.stacked = multibar.stacked(); - dispatch.stateChange(state); - selection.transition().call(chart); }); dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e, that.parentNode) + if (tooltips) showTooltip(e, that.parentNode); }); - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - if (typeof e.stacked !== 'undefined') { - multibar.stacked(e.stacked); - state.stacked = e.stacked; - } + //============================================================ - selection.call(chart); - }); //============================================================ + // Functions + //------------------------------------------------------------ + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.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); + } - }); - return chart; - } + 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 = x.range()[1] - 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); + }); + } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - multibar.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); - multibar.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); - }); - //============================================================ + dispatch.brush({extent: extent, brush: brush}); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + updateBrushBG(); - // expose chart's sub-components - chart.dispatch = dispatch; - chart.multibar = multibar; - chart.legend = legend; + // Update Main (Focus) + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum( + data + .filter(function(d) { return !d.disabled }) + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + d3.transition(focusLinesWrap).call(lines); + + + // Update Main (Focus) Axes + d3.transition(g.select('.nv-focus .nv-x.nv-axis')) + .call(xAxis); + d3.transition(g.select('.nv-focus .nv-y.nv-axis')) + .call(yAxis); + } + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; chart.xAxis = xAxis; chart.yAxis = yAxis; + chart.x2Axis = x2Axis; + chart.y2Axis = y2Axis; - d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay'); + d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.x = function(_) { + if (!arguments.length) return lines.x; + lines.x(_); + lines2.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return lines.y; + lines.y(_); + lines2.y(_); + return chart; + }; chart.margin = function(_) { if (!arguments.length) return margin; @@ -5992,6 +6358,12 @@ nv.models.multiBarChart = function() { return chart; }; + chart.margin2 = function(_) { + if (!arguments.length) return margin2; + margin2 = _; + return chart; + }; + chart.width = function(_) { if (!arguments.length) return width; width = _; @@ -6004,16 +6376,16 @@ nv.models.multiBarChart = function() { return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - legend.color(color); + chart.height2 = function(_) { + if (!arguments.length) return height2; + height2 = _; return chart; }; - chart.showControls = function(_) { - if (!arguments.length) return showControls; - showControls = _; + chart.color = function(_) { + if (!arguments.length) return color; + color =nv.utils.getColor(_); + legend.color(color); return chart; }; @@ -6023,24 +6395,6 @@ nv.models.multiBarChart = function() { return chart; }; - chart.reduceXTicks= function(_) { - if (!arguments.length) return reduceXTicks; - reduceXTicks = _; - return chart; - }; - - chart.rotateLabels = function(_) { - if (!arguments.length) return rotateLabels; - rotateLabels = _; - return chart; - } - - chart.tooltip = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; - return chart; - }; - chart.tooltips = function(_) { if (!arguments.length) return tooltips; tooltips = _; @@ -6053,9 +6407,10 @@ nv.models.multiBarChart = function() { return chart; }; - chart.state = function(_) { - if (!arguments.length) return state; - state = _; + chart.interpolate = function(_) { + if (!arguments.length) return lines.interpolate(); + lines.interpolate(_); + lines2.interpolate(_); return chart; }; @@ -6065,36 +6420,73 @@ nv.models.multiBarChart = function() { return chart; }; + // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below + chart.xTickFormat = function(_) { + if (!arguments.length) return xAxis.tickFormat(); + xAxis.tickFormat(_); + x2Axis.tickFormat(_); + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yAxis.tickFormat(); + yAxis.tickFormat(_); + y2Axis.tickFormat(_); + return chart; + }; + //============================================================ return chart; } -nv.models.multiBarHorizontal = function() { +nv.models.multiBarChart = 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 } - , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + var multibar = nv.models.multiBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 30, left: 60} + , width = null + , height = null , color = nv.utils.defaultColor() - , stacked = false - , showValues = false - , valuePadding = 60 - , valueFormat = d3.format(',.2f') - , delay = 1200 - , xDomain - , yDomain - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + , showControls = true + , showLegend = true + , reduceXTicks = true // if false a tick will show for every data point + , rotateLabels = 0 + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

' + key + '

' + + '

' + y + ' on ' + x + '

' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: false } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + multibar + .stacked(false) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) ; //============================================================ @@ -6104,60 +6496,62 @@ nv.models.multiBarHorizontal = function() { // Private Variables //------------------------------------------------------------ - var x0, y0 //used to store previous scales - ; + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; //============================================================ function chart(selection) { selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom, - container = d3.select(this); + var container = d3.select(this), + that = this; + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; - if (stacked) - data = d3.layout.stack() - .offset('zero') - .values(function(d){ return d.values }) - .y(getY) - (data); + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; - //add series index to each data point for reference - data = data.map(function(series, i) { - series.values = series.values.map(function(point) { - point.series = i; - return point; - }); - return series; - }); + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ //------------------------------------------------------------ // Setup Scales - // remap and flatten the data for use in calculating the scales' domains - var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate - data.map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i), y0: d.y0 } - }) - }); - - x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) - .rangeBands([0, availableHeight], .1); - - y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) - - if (showValues && !stacked) - y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); - else - y.range([0, availableWidth]); - - x0 = x0 || x; - y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + x = multibar.xScale(); + y = multibar.yScale(); //------------------------------------------------------------ @@ -6165,273 +6559,318 @@ nv.models.multiBarHorizontal = function() { //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-groups'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); //------------------------------------------------------------ + //------------------------------------------------------------ + // Legend - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - d3.transition(groups.exit()) - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6) - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .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) }); - d3.transition(groups) - .style('stroke-opacity', 1) - .style('fill-opacity', .75); + if (showLegend) { + legend.width(availableWidth / 2); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - var bars = groups.selectAll('g.nv-bar') - .data(function(d) { return d.values }); + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } - bars.exit().remove(); + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + //------------------------------------------------------------ - var barsEnter = bars.enter().append('g') - .attr('transform', function(d,i,j) { - return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' - }); - barsEnter.append('rect') - .attr('width', 0) - .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) + //------------------------------------------------------------ + // Controls - bars - .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - value: getY(d,i), - point: d, - series: data[d.series], - pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ], - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - value: getY(d,i), - point: d, - series: data[d.series], - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - value: getY(d,i), - point: d, - series: data[d.series], - pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - value: getY(d,i), - point: d, - series: data[d.series], - pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: d.series, - e: d3.event - }); - d3.event.stopPropagation(); - }); + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; - if (showValues && !stacked) { - barsEnter.append('text') - .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) - bars.select('text') - .attr('y', x.rangeBand() / 2) - .attr('dy', '-.32em') - .text(function(d,i) { return valueFormat(getY(d,i)) }) - d3.transition(bars) - //.delay(function(d,i) { return i * delay / data[0].values.length }) - .select('text') - .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) - } else { - bars.selectAll('text').remove(); + controls.width(180).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); } - bars - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - //.attr('transform', function(d,i,j) { - //return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + x(getX(d,i)) + ')' - //}) - if (stacked) - d3.transition(bars) - //.delay(function(d,i) { return i * delay / data[0].values.length }) - .attr('transform', function(d,i) { - //return 'translate(' + y(d.y0) + ',0)' - return 'translate(' + y(d.y0) + ',' + x(getX(d,i)) + ')' - }) - .select('rect') - .attr('width', function(d,i) { - return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) - }) - .attr('height', x.rangeBand() ); - else - d3.transition(bars) - //.delay(function(d,i) { return i * delay / data[0].values.length }) - .attr('transform', function(d,i) { - //TODO: stacked must be all positive or all negative, not both? - return 'translate(' + - (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) - + ',' + - (d.series * x.rangeBand() / data.length - + - x(getX(d,i)) ) - + ')' - }) - .select('rect') - .attr('height', x.rangeBand() / data.length ) - .attr('width', function(d,i) { - return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) - }); + //------------------------------------------------------------ - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - }); - return chart; - } + //------------------------------------------------------------ + // Main Chart Component(s) + multibar + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - chart.dispatch = dispatch; + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) - chart.x = function(_) { - if (!arguments.length) return getX; - getX = _; - return chart; - }; + d3.transition(barsWrap).call(multibar); - chart.y = function(_) { - if (!arguments.length) return getY; - getY = _; - return chart; - }; + //------------------------------------------------------------ - chart.margin = function(_) { - if (!arguments.length) return margin; - 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; - return chart; - }; - chart.width = function(_) { - if (!arguments.length) return width; - width = _; - return chart; - }; + //------------------------------------------------------------ + // Setup Axes - chart.height = function(_) { - if (!arguments.length) return height; - height = _; - return chart; - }; + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); - chart.xScale = function(_) { - if (!arguments.length) return x; - x = _; - return chart; - }; + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); - chart.yScale = function(_) { - if (!arguments.length) return y; - y = _; - return chart; - }; + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + + if(rotateLabels) + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' }) + .attr('text-transform', rotateLabels > 0 ? 'start' : 'end'); + + g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') + .style('opacity', 1); + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode) + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + selection.call(chart); + }); + + //============================================================ + + + }); - chart.xDomain = function(_) { - if (!arguments.length) return xDomain; - xDomain = _; return chart; - }; + } - chart.yDomain = function(_) { - if (!arguments.length) return yDomain; - yDomain = _; + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; return chart; }; - chart.forceY = function(_) { - if (!arguments.length) return forceY; - forceY = _; + chart.width = function(_) { + if (!arguments.length) return width; + width = _; return chart; }; - chart.stacked = function(_) { - if (!arguments.length) return stacked; - stacked = _; + chart.height = function(_) { + if (!arguments.length) return height; + height = _; return chart; }; chart.color = function(_) { if (!arguments.length) return color; color = nv.utils.getColor(_); + legend.color(color); return chart; }; - chart.id = function(_) { - if (!arguments.length) return id; - id = _; + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; return chart; }; - chart.delay = function(_) { - if (!arguments.length) return delay; - delay = _; + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; return chart; }; - chart.showValues = function(_) { - if (!arguments.length) return showValues; - showValues = _; + chart.reduceXTicks= function(_) { + if (!arguments.length) return reduceXTicks; + reduceXTicks = _; return chart; }; - chart.valueFormat= function(_) { - if (!arguments.length) return valueFormat; - valueFormat = _; + chart.rotateLabels = function(_) { + if (!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; return chart; }; - chart.valuePadding = function(_) { - if (!arguments.length) return valuePadding; - valuePadding = _; + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; return chart; }; @@ -6849,407 +7288,284 @@ nv.models.multiBarHorizontalChart = function() { return chart; } -nv.models.multiChart = function() { + +nv.models.multiBarHorizontal = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var margin = {top: 30, right: 20, bottom: 50, left: 60}, - color = d3.scale.category20().range(), - width = null, - height = null, - showLegend = true, - tooltips = true, - tooltip = function(key, x, y, e, graph) { - return '

' + key + '

' + - '

' + y + ' at ' + x + '

' - }, - x, y; //can be accessed via chart.lines.[x/y]Scale() + 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 } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , stacked = false + , showValues = false + , valuePadding = 60 + , valueFormat = d3.format(',.2f') + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x = d3.scale.linear(), - yScale1 = d3.scale.linear(), - yScale2 = d3.scale.linear(), - - lines1 = nv.models.line().yScale(yScale1), - lines2 = nv.models.line().yScale(yScale2), - - bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), - bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), - stack1 = nv.models.stackedArea().yScale(yScale1), - stack2 = nv.models.stackedArea().yScale(yScale2), - xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), - yAxis1 = nv.models.axis().scale(yScale1).orient('left'), - yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + //============================================================ + // Private Variables + //------------------------------------------------------------ - legend = nv.models.legend().height(30), - dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + var x0, y0 //used to store previous scales + ; - var showTooltip = function(e, offsetElement) { - var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), - top = e.pos[1] + ( offsetElement.offsetTop || 0), - x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)), - y = (e.series.bar ? yAxis1 : yAxis2).tickFormat()(lines1.y()(e.point, e.pointIndex)), - content = tooltip(e.series.key, x, y, e, chart); + //============================================================ - nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent); - }; function chart(selection) { selection.each(function(data) { - var container = d3.select(this), - that = this; - - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - - var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1}) - var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2}) - var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1}) - var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2}) - var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1}) - var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2}) - - var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: d.x, y: d.y } - }) - }) - - var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: d.x, y: d.y } - }) - }) - - 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]); - var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); - - gEnter.append('g').attr('class', 'x axis'); - gEnter.append('g').attr('class', 'y1 axis'); - gEnter.append('g').attr('class', 'y2 axis'); - gEnter.append('g').attr('class', 'lines1Wrap'); - gEnter.append('g').attr('class', 'lines2Wrap'); - gEnter.append('g').attr('class', 'bars1Wrap'); - gEnter.append('g').attr('class', 'bars2Wrap'); - gEnter.append('g').attr('class', 'stack1Wrap'); - gEnter.append('g').attr('class', 'stack2Wrap'); - gEnter.append('g').attr('class', 'legendWrap'); - - var g = wrap.select('g'); - - if (showLegend) { - legend.width( availableWidth / 2 ); + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); - g.select('.legendWrap') - .datum(data.map(function(series) { - series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; - series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); - return series; - })) - .call(legend); - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); - g.select('.legendWrap') - .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); - } + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); - lines1 - .width(availableWidth) - .height(availableHeight) - .interpolate("monotone") - .color(data.map(function(d,i) { - return d.color || color[i % color.length]; - }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); - lines2 - .width(availableWidth) - .height(availableHeight) - .interpolate("monotone") - .color(data.map(function(d,i) { - return d.color || color[i % color.length]; - }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + //------------------------------------------------------------ + // Setup Scales - bars1 - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color[i % color.length]; - }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); - bars2 - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color[i % color.length]; - }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableHeight], .1); - stack1 - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color[i % color.length]; - }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) - stack2 - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color[i % color.length]; - }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + if (showValues && !stacked) + y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); + else + y.range([0, availableWidth]); - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + x0 = x0 || x; + y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + //------------------------------------------------------------ - var lines1Wrap = g.select('.lines1Wrap') - .datum(dataLines1) - var bars1Wrap = g.select('.bars1Wrap') - .datum(dataBars1) - var stack1Wrap = g.select('.stack1Wrap') - .datum(dataStack1) - var lines2Wrap = g.select('.lines2Wrap') - .datum(dataLines2) - var bars2Wrap = g.select('.bars2Wrap') - .datum(dataBars2) - var stack2Wrap = g.select('.stack2Wrap') - .datum(dataStack2) + //------------------------------------------------------------ + // Setup containers and skeleton of chart - var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ - return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) - }).concat([{x:0, y:0}]) : [] - var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ - return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) - }).concat([{x:0, y:0}]) : [] + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) - .range([0, availableHeight]) + gEnter.append('g').attr('class', 'nv-groups'); - yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) - .range([0, availableHeight]) + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - lines1.yDomain(yScale1.domain()) - bars1.yDomain(yScale1.domain()) - stack1.yDomain(yScale1.domain()) + //------------------------------------------------------------ - lines2.yDomain(yScale2.domain()) - bars2.yDomain(yScale2.domain()) - stack2.yDomain(yScale2.domain()) - if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} - if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} - if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} - if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .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) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); - if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} - if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} - + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); - xAxis - .ticks( availableWidth / 100 ) - .tickSize(-availableHeight, 0); + bars.exit().remove(); - g.select('.x.axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - d3.transition(g.select('.x.axis')) - .call(xAxis); - yAxis1 - .ticks( availableHeight / 36 ) - .tickSize( -availableWidth, 0); + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' + }); + barsEnter.append('rect') + .attr('width', 0) + .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) - d3.transition(g.select('.y1.axis')) - .call(yAxis1); - - yAxis2 - .ticks( availableHeight / 36 ) - .tickSize( -availableWidth, 0); - - d3.transition(g.select('.y2.axis')) - .call(yAxis2); + bars + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); - g.select('.y2.axis') - .style('opacity', series2.length ? 1 : 0) - .attr('transform', 'translate(' + x.range()[1] + ',0)'); + if (showValues && !stacked) { + barsEnter.append('text') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) + bars.select('text') + .attr('y', x.rangeBand() / 2) + .attr('dy', '-.32em') + .text(function(d,i) { return valueFormat(getY(d,i)) }) + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .select('text') + .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) + } else { + bars.selectAll('text').remove(); + } - legend.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + //.attr('transform', function(d,i,j) { + //return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + x(getX(d,i)) + ')' + //}) + if (stacked) + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('transform', function(d,i) { + //return 'translate(' + y(d.y0) + ',0)' + return 'translate(' + y(d.y0) + ',' + x(getX(d,i)) + ')' + }) + .select('rect') + .attr('width', function(d,i) { + return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) + }) + .attr('height', x.rangeBand() ); + else + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('transform', function(d,i) { + //TODO: stacked must be all positive or all negative, not both? + return 'translate(' + + (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) + + ',' + + (d.series * x.rangeBand() / data.length + + + x(getX(d,i)) ) + + ')' + }) + .select('rect') + .attr('height', x.rangeBand() / data.length ) + .attr('width', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) + }); - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - wrap.selectAll('.series').classed('disabled', false); - return d; - }); - } - selection.transition().call(chart); - }); - dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e, that.parentNode); - }); + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); }); - chart.update = function() { chart(selection) }; - chart.container = this; - return chart; } //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - lines1.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - lines1.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - lines2.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - lines2.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - bars1.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - bars1.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - bars2.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - bars2.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - stack1.dispatch.on('tooltipShow', function(e) { - //disable tooltips when value ~= 0 - //// TODO: consider removing points from voronoi that have 0 value instead of this hack - if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range - setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); - return false; - } - - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], - dispatch.tooltipShow(e); - }); - - stack1.dispatch.on('tooltipHide', function(e) { - dispatch.tooltipHide(e); - }); - - stack2.dispatch.on('tooltipShow', function(e) { - //disable tooltips when value ~= 0 - //// TODO: consider removing points from voronoi that have 0 value instead of this hack - if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range - setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); - return false; - } - - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], - dispatch.tooltipShow(e); - }); - - stack2.dispatch.on('tooltipHide', function(e) { - dispatch.tooltipHide(e); - }); - - lines1.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - lines1.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - lines2.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); - - lines2.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); - - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); - }); - - - - //============================================================ - // Global getters and setters + // Expose Public Variables //------------------------------------------------------------ chart.dispatch = dispatch; - chart.lines1 = lines1; - chart.lines2 = lines2; - chart.bars1 = bars1; - chart.bars2 = bars2; - chart.stack1 = stack1; - chart.stack2 = stack2; - chart.xAxis = xAxis; - chart.yAxis1 = yAxis1; - chart.yAxis2 = yAxis2; chart.x = function(_) { if (!arguments.length) return getX; getX = _; - lines1.x(_); - bars1.x(_); return chart; }; chart.y = function(_) { if (!arguments.length) return getY; getY = _; - lines1.y(_); - bars1.y(_); return chart; }; chart.margin = function(_) { if (!arguments.length) return margin; - margin = _; + 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; return chart; }; @@ -7265,36 +7581,85 @@ nv.models.multiChart = function() { return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = _; - legend.color(_); + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; return chart; }; - chart.showLegend = function(_) { - if (!arguments.length) return showLegend; - showLegend = _; - return chart; - }; + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; - chart.tooltips = function(_) { - if (!arguments.length) return tooltips; - tooltips = _; + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; return chart; }; - chart.tooltipContent = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.valuePadding = function(_) { + if (!arguments.length) return valuePadding; + valuePadding = _; return chart; }; + //============================================================ + + return chart; } - -nv.models.ohlcBar = function() { +nv.models.multiBar = function() { //============================================================ // Public Variables with Default Settings @@ -7303,20 +7668,16 @@ nv.models.ohlcBar = function() { 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.linear() + , x = d3.scale.ordinal() , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one , getX = function(d) { return d.x } , getY = function(d) { return d.y } - , getOpen = function(d) { return d.open } - , getClose = function(d) { return d.close } - , getHigh = function(d) { return d.high } - , getLow = function(d) { return d.low } - , forceX = [] - , forceY = [] - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove , clipEdge = true + , stacked = false , color = nv.utils.defaultColor() + , delay = 1200 , xDomain , yDomain , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') @@ -7324,11 +7685,13 @@ nv.models.ohlcBar = function() { //============================================================ + //============================================================ // Private Variables //------------------------------------------------------------ - //TODO: store old scales for transitions + var x0, y0 //used to store previous scales + ; //============================================================ @@ -7339,23 +7702,42 @@ nv.models.ohlcBar = function() { availableHeight = height - margin.top - margin.bottom, container = d3.select(this); + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + //------------------------------------------------------------ // Setup Scales - x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); - if (padData) - x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range([0, availableWidth]); + x .domain(d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableWidth], .1); - y .domain(yDomain || [ - d3.min(data[0].values.map(getLow).concat(forceY)), - d3.max(data[0].values.map(getHigh).concat(forceY)) - ]) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) .range([availableHeight, 0]); + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; if (x.domain()[0] === x.domain()[1]) @@ -7368,161 +7750,172 @@ nv.models.ohlcBar = function() { y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) : y.domain([-1,1]); + + x0 = x0 || x; + y0 = y0 || y; + //------------------------------------------------------------ //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); var defsEnter = wrapEnter.append('defs'); var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + var g = wrap.select('g') - gEnter.append('g').attr('class', 'nv-ticks'); + gEnter.append('g').attr('class', 'nv-groups'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) + .attr('id', 'nv-edge-clip-' + id) .append('rect'); - - wrap.select('#nv-chart-clip-path-' + id + ' rect') + wrap.select('#nv-edge-clip-' + id + ' rect') .attr('width', availableWidth) .attr('height', availableHeight); - g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') - .data(function(d) { return d }); + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + //.style('stroke-opacity', 1e-6) + //.style('fill-opacity', 1e-6) + .selectAll('rect.nv-bar') + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) + .attr('height', 0) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .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) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); - ticks.exit().remove(); + var bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return d.values }); - var ticksEnter = ticks.enter().append('path') - .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) - .attr('d', function(d,i) { - var w = (availableWidth / data[0].values.length) * .9; - return 'm0,0l0,' - + (y(getOpen(d,i)) - - y(getHigh(d,i))) - + 'l' - + (-w/2) - + ',0l' - + (w/2) - + ',0l0,' - + (y(getLow(d,i)) - y(getOpen(d,i))) - + 'l0,' - + (y(getClose(d,i)) - - y(getLow(d,i))) - + 'l' - + (w/2) - + ',0l' - + (-w/2) - + ',0z'; + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('x', function(d,i,j) { + return stacked ? 0 : (j * x.rangeBand() / data.length ) }) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) - //.attr('fill', function(d,i) { return color[0]; }) - //.attr('stroke', function(d,i) { return color[0]; }) - //.attr('x', 0 ) - //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) - //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) - .on('mouseover', function(d,i) { + .attr('y', function(d) { return y0(stacked ? d.y0 : 0) }) + .attr('height', 0) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + bars + .style('fill', function(d,i,j){ return color(d, j, i); }) + .style('stroke', function(d,i,j){ return color(d, j, i); }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here d3.select(this).classed('hover', true); dispatch.elementMouseover({ - point: d, - series: data[0], - pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted - pointIndex: i, - seriesIndex: 0, - e: d3.event + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event }); - }) .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - point: d, - series: data[0], - pointIndex: i, - seriesIndex: 0, - e: d3.event - }); + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); }) .on('click', function(d,i) { - dispatch.elementClick({ - //label: d[label], - value: getY(d,i), - data: d, - index: i, - pos: [x(getX(d,i)), y(getY(d,i))], - e: d3.event, - id: id - }); - d3.event.stopPropagation(); + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); }) .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - //label: d[label], - value: getY(d,i), - data: d, - index: i, - pos: [x(getX(d,i)), y(getY(d,i))], - e: d3.event, - id: id - }); - d3.event.stopPropagation(); + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); }); - - ticks - .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) - d3.transition(ticks) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) - .attr('d', function(d,i) { - var w = (availableWidth / data[0].values.length) * .9; - return 'm0,0l0,' - + (y(getOpen(d,i)) - - y(getHigh(d,i))) - + 'l' - + (-w/2) - + ',0l' - + (w/2) - + ',0l0,' - + (y(getLow(d,i)) - - y(getOpen(d,i))) - + 'l0,' - + (y(getClose(d,i)) - - y(getLow(d,i))) - + 'l' - + (w/2) - + ',0l' - + (-w/2) - + ',0z'; - }) - //.attr('width', (availableWidth / data[0].values.length) * .9 ) + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) + if (stacked) + d3.transition(bars) + .delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('y', function(d,i) { + return y(getY(d,i) + (stacked ? d.y0 : 0)); + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1); + }) + .each('end', function() { + d3.transition(d3.select(this)) + .attr('x', function(d,i) { + return stacked ? 0 : (d.series * x.rangeBand() / data.length ) + }) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + }) + else + d3.transition(bars) + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('x', function(d,i) { + return d.series * x.rangeBand() / data.length + }) + .attr('width', x.rangeBand() / data.length) + .each('end', function() { + d3.transition(d3.select(this)) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1); + }); + }) - //d3.transition(ticks) - //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) - //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); - //.order(); // not sure if this makes any sense for this model + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); }); @@ -7548,30 +7941,6 @@ nv.models.ohlcBar = function() { return chart; }; - chart.open = function(_) { - if (!arguments.length) return getOpen; - getOpen = _; - return chart; - }; - - chart.close = function(_) { - if (!arguments.length) return getClose; - getClose = _; - return chart; - }; - - chart.high = function(_) { - if (!arguments.length) return getHigh; - getHigh = _; - return chart; - }; - - chart.low = function(_) { - if (!arguments.length) return getLow; - getLow = _; - return chart; - }; - chart.margin = function(_) { if (!arguments.length) return margin; margin.top = typeof _.top != 'undefined' ? _.top : margin.top; @@ -7617,21 +7986,15 @@ nv.models.ohlcBar = function() { return chart; }; - chart.forceX = function(_) { - if (!arguments.length) return forceX; - forceX = _; - return chart; - }; - chart.forceY = function(_) { if (!arguments.length) return forceY; forceY = _; return chart; }; - chart.padData = function(_) { - if (!arguments.length) return padData; - padData = _; + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; return chart; }; @@ -7653,504 +8016,295 @@ nv.models.ohlcBar = function() { return chart; }; + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + //============================================================ return chart; } - -nv.models.pie = function() { +nv.models.multiBarTimeSeriesChart = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 500 - , height = 500 - , getValues = function(d) { return d.values } - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + var multibar = nv.models.multiBarTimeSeries() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null , color = nv.utils.defaultColor() - , valueFormat = d3.format(',.2f') - , showLabels = true - , donutLabelsOutside = false - , labelThreshold = .02 //if slice percentage is under this, don't show label - , donut = false - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + , showControls = true + , showLegend = true + , reduceXTicks = true // if false a tick will show for every data point + , rotateLabels = 0 + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

' + key + '

' + + '

' + y + ' on ' + x + '

' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + multibar + .stacked(false) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + .showMaxMin(false) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) ; //============================================================ - function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom, - radius = Math.min(availableWidth, availableHeight) / 2, - container = d3.select(this); + //============================================================ + // Private Variables + //------------------------------------------------------------ + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); - //------------------------------------------------------------ - // Setup containers and skeleton of chart + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; - //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]); - var wrap = container.selectAll('.nv-wrap.nv-pie').data([getValues(data[0])]); - var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + //============================================================ - gEnter.append('g').attr('class', 'nv-pie'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; - //------------------------------------------------------------ + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); + //------------------------------------------------------------ + // Display noData message if there's nothing to show. - var arc = d3.svg.arc() - .outerRadius((radius-(radius / 5))); - - if (donut) arc.innerRadius(radius / 2); - + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); - // Setup the Pie chart and choose the data element - var pie = d3.layout.pie() - .sort(null) - .value(function(d) { return d.disabled ? 0 : getY(d) }); + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); - var slices = wrap.select('.nv-pie').selectAll('.nv-slice') - .data(pie); + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); - slices.exit().remove(); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } - var ae = slices.enter().append('g') - .attr('class', 'nv-slice') - .on('mouseover', function(d,i){ - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - label: getX(d.data), - value: getY(d.data), - point: d.data, - pointIndex: i, - pos: [d3.event.pageX, d3.event.pageY], - id: id - }); - }) - .on('mouseout', function(d,i){ - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - label: getX(d.data), - value: getY(d.data), - point: d.data, - index: i, - id: id - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - label: getX(d.data), - value: getY(d.data), - point: d.data, - index: i, - pos: d3.event, - id: id - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - label: getX(d.data), - value: getY(d.data), - point: d.data, - index: i, - pos: d3.event, - id: id - }); - d3.event.stopPropagation(); - }); + //------------------------------------------------------------ - slices - .attr('fill', function(d,i) { return color(d, i); }) - .attr('stroke', function(d,i) { return color(d, i); }); - var paths = ae.append('path') - .each(function(d) { this._current = d; }); - //.attr('d', arc); + //------------------------------------------------------------ + // Setup Scales - d3.transition(slices.select('path')) - .attr('d', arc) - .attrTween('d', arcTween); + x = multibar.xScale(); + y = multibar.yScale(); - if (showLabels) { - // This does the normal label - var labelsArc = arc; - if (donutLabelsOutside) { - labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()) - } + //------------------------------------------------------------ - ae.append("g").classed("nv-label", true) - .each(function(d, i) { - var group = d3.select(this); - group - .attr('transform', function(d) { - d.outerRadius = radius + 10; // Set Outer Coordinate - d.innerRadius = radius + 15; // Set Inner Coordinate - return 'translate(' + labelsArc.centroid(d) + ')' - }); + //------------------------------------------------------------ + // Setup containers and skeleton of chart - group.append('rect') - .style('stroke', '#fff') - .style('fill', '#fff') - .attr("rx", 3) - .attr("ry", 3); + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var g = wrap.select('g'); - group.append('text') - .style('text-anchor', 'middle') //center the text on it's origin - .style('fill', '#000') + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + //------------------------------------------------------------ - }); - slices.select(".nv-label").transition() - .attr('transform', function(d) { - d.outerRadius = radius + 10; // Set Outer Coordinate - d.innerRadius = radius + 15; // Set Inner Coordinate - return 'translate(' + labelsArc.centroid(d) + ')'; - }); + //------------------------------------------------------------ + // Legend - slices.each(function(d, i) { - var slice = d3.select(this); + if (showLegend) { + legend.width(availableWidth / 2); - slice - .select(".nv-label text") - .text(function(d, i) { - var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); - return (d.value && percent > labelThreshold) ? getX(d.data) : ''; - }); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - var textBox = slice.select('text').node().getBBox(); - slice.select(".nv-label rect") - .attr("width", textBox.width + 10) - .attr("height", textBox.height + 10) - .attr("transform", function() { - return "translate(" + [textBox.x - 5, textBox.y - 5] + ")"; - }); - }); + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; } + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } - // Computes the angle of an arc, converting from radians to degrees. - function angle(d) { - var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; - return a > 90 ? a - 180 : a; - } + //------------------------------------------------------------ - function arcTween(a) { - if (!donut) a.innerRadius = 0; - var i = d3.interpolate(this._current, a); - this._current = i(0); - return function(t) { - return arc(i(t)); - }; - } - function tweenPie(b) { - b.innerRadius = 0; - var i = d3.interpolate({startAngle: 0, endAngle: 0}, b); - return function(t) { - return arc(i(t)); - }; - } + //------------------------------------------------------------ + // Controls - }); + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; - return chart; - } + controls.width(180).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + //------------------------------------------------------------ - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - chart.dispatch = dispatch; + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - chart.margin = function(_) { - if (!arguments.length) return margin; - 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; - return chart; - }; - chart.width = function(_) { - if (!arguments.length) return width; - width = _; - return chart; - }; + //------------------------------------------------------------ + // Main Chart Component(s) - chart.height = function(_) { - if (!arguments.length) return height; - height = _; - return chart; - }; + multibar + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) - chart.values = function(_) { - if (!arguments.length) return getValues; - getValues = _; - return chart; - }; - chart.x = function(_) { - if (!arguments.length) return getX; - getX = _; - return chart; - }; + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) - chart.y = function(_) { - if (!arguments.length) return getY; - getY = d3.functor(_); - return chart; - }; + d3.transition(barsWrap).call(multibar); - chart.showLabels = function(_) { - if (!arguments.length) return showLabels; - showLabels = _; - return chart; - }; - - chart.donutLabelsOutside = function(_) { - if (!arguments.length) return donutLabelsOutside; - donutLabelsOutside = _; - return chart; - }; - - chart.donut = function(_) { - if (!arguments.length) return donut; - donut = _; - return chart; - }; - - chart.id = function(_) { - if (!arguments.length) return id; - id = _; - return chart; - }; - - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - return chart; - }; - - chart.valueFormat = function(_) { - if (!arguments.length) return valueFormat; - valueFormat = _; - return chart; - }; - - chart.labelThreshold = function(_) { - if (!arguments.length) return labelThreshold; - labelThreshold = _; - return chart; - }; - - //============================================================ - - - return chart; -} - -nv.models.pieChart = function() { - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var pie = nv.models.pie() - , legend = nv.models.legend() - ; - - var margin = {top: 30, right: 20, bottom: 20, left: 20} - , width = null - , height = null - , showLegend = true - , color = nv.utils.defaultColor() - , tooltips = true - , tooltip = function(key, y, e, graph) { - return '

' + key + '

' + - '

' + y + '

' - } - , state = {} - , noData = "No Data Available." - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') - ; - - //============================================================ - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var showTooltip = function(e, offsetElement) { - var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ), - top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0), - y = pie.valueFormat()(pie.y()(e.point)), - content = tooltip(pie.x()(e.point), y, e, chart); - - nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); - }; - - //============================================================ - - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - - chart.update = function() { chart(selection); }; - chart.container = this; - - - //------------------------------------------------------------ - // Display No Data message if there's nothing to show. - - if (!data || !data.length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); - - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); - - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); - - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Setup containers and skeleton of chart - - var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-pieWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - - //------------------------------------------------------------ + //------------------------------------------------------------ //------------------------------------------------------------ - // Legend + // Setup Axes - if (showLegend) { - legend - .width( availableWidth ) - .key(pie.x()); + xAxis + .scale(x) + .ticks(availableWidth / 100) + .tickSize(-availableHeight, 0); - wrap.select('.nv-legendWrap') - .datum(pie.values()(data[0])) - .call(legend); + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')'); - } + xTicks + .selectAll('line, text') + .style('opacity', 1) - //------------------------------------------------------------ + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + if(rotateLabels) + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' }) + .attr('text-transform', rotateLabels > 0 ? 'start' : 'end'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); //------------------------------------------------------------ - // Main Chart Component(s) - - pie - .width(availableWidth) - .height(availableHeight); - - - var pieWrap = g.select('.nv-pieWrap') - .datum(data); - - d3.transition(pieWrap).call(pie); - //------------------------------------------------------------ //============================================================ // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ - legend.dispatch.on('legendClick', function(d,i, that) { + legend.dispatch.on('legendClick', function(d,i) { d.disabled = !d.disabled; - if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) { - pie.values()(data[0]).map(function(d) { + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { d.disabled = false; wrap.selectAll('.nv-series').classed('disabled', false); return d; }); } - state.disabled = data[0].map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - - selection.transition().call(chart) - }); - - pie.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); + selection.transition().call(chart); }); - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - - if (typeof e.disabled !== 'undefined') { - data[0].forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; - state.disabled = e.disabled; + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; } - selection.call(chart); + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode) }); //============================================================ @@ -8161,19 +8315,19 @@ nv.models.pieChart = function() { return chart; } + //============================================================ // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - pie.dispatch.on('elementMouseover.tooltip', function(e) { + multibar.dispatch.on('elementMouseover.tooltip', function(e) { e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; dispatch.tooltipShow(e); }); - dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e); + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); }); - dispatch.on('tooltipHide', function() { if (tooltips) nv.tooltip.cleanup(); }); @@ -8186,11 +8340,13 @@ nv.models.pieChart = function() { //------------------------------------------------------------ // expose chart's sub-components - chart.legend = legend; chart.dispatch = dispatch; - chart.pie = pie; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; - d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'id', 'showLabels', 'donutLabelsOutside', 'donut', 'labelThreshold'); + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay'); chart.margin = function(_) { if (!arguments.length) return margin; @@ -8217,7 +8373,12 @@ nv.models.pieChart = function() { if (!arguments.length) return color; color = nv.utils.getColor(_); legend.color(color); - pie.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; return chart; }; @@ -8227,21 +8388,33 @@ nv.models.pieChart = function() { return chart; }; - chart.tooltips = function(_) { - if (!arguments.length) return tooltips; - tooltips = _; + chart.reduceXTicks= function(_) { + if (!arguments.length) return reduceXTicks; + reduceXTicks = _; return chart; }; - chart.tooltipContent = function(_) { + chart.rotateLabels = function(_) { + if (!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.tooltip = function(_) { if (!arguments.length) return tooltip; tooltip = _; return chart; }; - chart.state = function(_) { - if (!arguments.length) return state; - state = _; + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; return chart; }; @@ -8257,41 +8430,28 @@ nv.models.pieChart = function() { return chart; } -nv.models.scatter = function() { +nv.models.multiBarTimeSeries = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , color = nv.utils.defaultColor() // chooses color - , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one - , x = d3.scale.linear() - , y = d3.scale.linear() - , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area - , getX = function(d) { return d.x } // accessor to get the x value - , getY = function(d) { return d.y } // accessor to get the y value - , getSize = function(d) { return d.size || 1} // accessor to get the point size - , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape - , onlyCircles = true // Set to false to use shapes - , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) - , forceY = [] // List of numbers to Force into the Y scale - , forceSize = [] // List of numbers to Force into the Size scale - , interactive = true // If true, plots a voronoi overlay for advanced point interection - , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart - , clipEdge = false // if true, masks points within x and y scale - , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance - , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips - , xDomain = null // Override x domain (skips the calculation from data) - , yDomain = null // Override y domain - , sizeDomain = null // Override point size domain - , sizeRange = null - , singlePoint = false - , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout') - , useVoronoi = true + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , x = d3.time.scale() + , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , clipEdge = true + , stacked = false + , color = nv.utils.defaultColor() + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') ; //============================================================ @@ -8301,10 +8461,8 @@ nv.models.scatter = function() { // Private Variables //------------------------------------------------------------ - var x0, y0, z0 // used to store previous scales - , timeoutID - , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips - ; + var x0, y0 //used to store previous scales + ; //============================================================ @@ -8315,6 +8473,14 @@ nv.models.scatter = function() { availableHeight = height - margin.top - margin.bottom, container = d3.select(this); + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + //add series index to each data point for reference data = data.map(function(series, i) { series.values = series.values.map(function(point) { @@ -8328,27 +8494,19 @@ nv.models.scatter = function() { // Setup Scales // 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) { - return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } - }) + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } }) - ); - - x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x }).concat(forceX))) + }); - if (padData) - x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range([0, availableWidth]); + x .domain(d3.extent(d3.merge(seriesData).map(function(d) { return d.x }))) + .range([0, availableWidth]); - y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) .range([availableHeight, 0]); - z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) - .range(sizeRange || [16, 256]); // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; @@ -8365,7 +8523,6 @@ nv.models.scatter = function() { x0 = x0 || x; y0 = y0 || y; - z0 = z0 || z; //------------------------------------------------------------ @@ -8373,24 +8530,23 @@ nv.models.scatter = function() { //------------------------------------------------------------ // 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 + (singlePoint ? ' nv-single-point' : '')); + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); var defsEnter = wrapEnter.append('defs'); var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + var g = wrap.select('g') gEnter.append('g').attr('class', 'nv-groups'); - gEnter.append('g').attr('class', 'nv-point-paths'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ + defsEnter.append('clipPath') .attr('id', 'nv-edge-clip-' + id) .append('rect'); - wrap.select('#nv-edge-clip-' + id + ' rect') .attr('width', availableWidth) .attr('height', availableHeight); @@ -8398,270 +8554,2080 @@ nv.models.scatter = function() { g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - function updateInteractiveLayer() { - if (!interactive) return false; + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + //.style('stroke-opacity', 1e-6) + //.style('fill-opacity', 1e-6) + .selectAll('rect.nv-bar') + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) + .attr('height', 0) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .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) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); - var eventElements; - var vertices = d3.merge(data.map(function(group, groupIndex) { - return group.values - .map(function(point, pointIndex) { - // *Adding noise to make duplicates very unlikely - // **Injecting series and point index for reference - return [x(getX(point,pointIndex)) * (Math.random() / 1e12 + 1) , y(getY(point,pointIndex)) * (Math.random() / 1e12 + 1), groupIndex, pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates - }) - .filter(function(pointArray, pointIndex) { - return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! - }) + var bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + var maxElements = 0; + for(var ei=0; ei' + key + '' + + '

' + y + ' at ' + x + '

' + }, + x, y; //can be accessed via chart.lines.[x/y]Scale() + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x = d3.scale.linear(), + yScale1 = d3.scale.linear(), + yScale2 = d3.scale.linear(), + + lines1 = nv.models.line().yScale(yScale1), + lines2 = nv.models.line().yScale(yScale2), + + bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), + bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), + + stack1 = nv.models.stackedArea().yScale(yScale1), + stack2 = nv.models.stackedArea().yScale(yScale2), + + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis1 = nv.models.axis().scale(yScale1).orient('left'), + yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + + legend = nv.models.legend().height(30), + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)), + y = (e.series.bar ? yAxis1 : yAxis2).tickFormat()(lines1.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent); + }; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1}) + var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2}) + var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1}) + var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2}) + var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1}) + var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2}) + + var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + 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]); + var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); + + gEnter.append('g').attr('class', 'x axis'); + gEnter.append('g').attr('class', 'y1 axis'); + gEnter.append('g').attr('class', 'y2 axis'); + gEnter.append('g').attr('class', 'lines1Wrap'); + gEnter.append('g').attr('class', 'lines2Wrap'); + gEnter.append('g').attr('class', 'bars1Wrap'); + gEnter.append('g').attr('class', 'bars2Wrap'); + gEnter.append('g').attr('class', 'stack1Wrap'); + gEnter.append('g').attr('class', 'stack2Wrap'); + gEnter.append('g').attr('class', 'legendWrap'); + + var g = wrap.select('g'); + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + + lines1 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); + + lines2 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + + bars1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + + bars2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + + stack1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + + stack2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + var lines1Wrap = g.select('.lines1Wrap') + .datum(dataLines1) + var bars1Wrap = g.select('.bars1Wrap') + .datum(dataBars1) + var stack1Wrap = g.select('.stack1Wrap') + .datum(dataStack1) + + var lines2Wrap = g.select('.lines2Wrap') + .datum(dataLines2) + var bars2Wrap = g.select('.bars2Wrap') + .datum(dataBars2) + var stack2Wrap = g.select('.stack2Wrap') + .datum(dataStack2) + + var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + + yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) + .range([0, availableHeight]) + + yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) + .range([0, availableHeight]) + + lines1.yDomain(yScale1.domain()) + bars1.yDomain(yScale1.domain()) + stack1.yDomain(yScale1.domain()) + + lines2.yDomain(yScale2.domain()) + bars2.yDomain(yScale2.domain()) + stack2.yDomain(yScale2.domain()) + + if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} + if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} + + if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} + if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + + if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} + if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} + + + + xAxis + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.x.axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.x.axis')) + .call(xAxis); + + yAxis1 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + + d3.transition(g.select('.y1.axis')) + .call(yAxis1); + + yAxis2 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.y2.axis')) + .call(yAxis2); + + g.select('.y2.axis') + .style('opacity', series2.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.series').classed('disabled', false); + return d; + }); + } + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + }); + + chart.update = function() { chart(selection) }; + chart.container = this; + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + stack1.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack1.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + stack2.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack2.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.lines1 = lines1; + chart.lines2 = lines2; + chart.bars1 = bars1; + chart.bars2 = bars2; + chart.stack1 = stack1; + chart.stack2 = stack2; + chart.xAxis = xAxis; + chart.yAxis1 = yAxis1; + chart.yAxis2 = yAxis2; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines1.x(_); + bars1.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines1.y(_); + bars1.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = _; + legend.color(_); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + return chart; +} + + +nv.models.ohlcBar = 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.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + //TODO: store old scales for transitions + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).concat(forceY)) + ]) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + + ticks.exit().remove(); + + + var ticksEnter = ticks.enter().append('path') + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + //.attr('fill', function(d,i) { return color[0]; }) + //.attr('stroke', function(d,i) { return color[0]; }) + //.attr('x', 0 ) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + ticks + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + d3.transition(ticks) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) + - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + //.attr('width', (availableWidth / data[0].values.length) * .9 ) + + + //d3.transition(ticks) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); + //.order(); // not sure if this makes any sense for this model + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.open = function(_) { + if (!arguments.length) return getOpen; + getOpen = _; + return chart; + }; + + chart.close = function(_) { + if (!arguments.length) return getClose; + getClose = _; + return chart; + }; + + chart.high = function(_) { + if (!arguments.length) return getHigh; + getHigh = _; + return chart; + }; + + chart.low = function(_) { + if (!arguments.length) return getLow; + getLow = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.pieChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var pie = nv.models.pie() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , showLegend = true + , color = nv.utils.defaultColor() + , tooltips = true + , tooltip = function(key, y, e, graph) { + return '

' + key + '

' + + '

' + y + '

' + } + , state = {} + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ), + top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0), + y = pie.valueFormat()(pie.y()(e.point)), + content = tooltip(pie.x()(e.point), y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection); }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pieWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend + .width( availableWidth ) + .key(pie.x()); + + wrap.select('.nv-legendWrap') + .datum(pie.values()(data[0])) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + pie + .width(availableWidth) + .height(availableHeight); + + + var pieWrap = g.select('.nv-pieWrap') + .datum(data); + + d3.transition(pieWrap).call(pie); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) { + pie.values()(data[0]).map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data[0].map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart) + }); + + pie.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data[0].forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + pie.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.legend = legend; + chart.dispatch = dispatch; + chart.pie = pie; + + d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'id', 'showLabels', 'donutLabelsOutside', 'donut', 'labelThreshold'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + pie.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.pie = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 500 + , height = 500 + , getValues = function(d) { return d.values } + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , color = nv.utils.defaultColor() + , valueFormat = d3.format(',.2f') + , showLabels = true + , donutLabelsOutside = false + , labelThreshold = .02 //if slice percentage is under this, don't show label + , donut = false + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + radius = Math.min(availableWidth, availableHeight) / 2, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]); + var wrap = container.selectAll('.nv-wrap.nv-pie').data([getValues(data[0])]); + var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pie'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + var arc = d3.svg.arc() + .outerRadius((radius-(radius / 5))); + + if (donut) arc.innerRadius(radius / 2); + + + // Setup the Pie chart and choose the data element + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.disabled ? 0 : getY(d) }); + + var slices = wrap.select('.nv-pie').selectAll('.nv-slice') + .data(pie); + + slices.exit().remove(); + + var ae = slices.enter().append('g') + .attr('class', 'nv-slice') + .on('mouseover', function(d,i){ + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + pointIndex: i, + pos: [d3.event.pageX, d3.event.pageY], + id: id + }); + }) + .on('mouseout', function(d,i){ + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + id: id + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + slices + .attr('fill', function(d,i) { return color(d, i); }) + .attr('stroke', function(d,i) { return color(d, i); }); + + var paths = ae.append('path') + .each(function(d) { this._current = d; }); + //.attr('d', arc); + + d3.transition(slices.select('path')) + .attr('d', arc) + .attrTween('d', arcTween); + + if (showLabels) { + // This does the normal label + var labelsArc = arc; + if (donutLabelsOutside) { + labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()) + } + + ae.append("g").classed("nv-label", true) + .each(function(d, i) { + var group = d3.select(this); + + group + .attr('transform', function(d) { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + }); + + group.append('rect') + .style('stroke', '#fff') + .style('fill', '#fff') + .attr("rx", 3) + .attr("ry", 3); + + group.append('text') + .style('text-anchor', 'middle') //center the text on it's origin + .style('fill', '#000') + + + }); + + slices.select(".nv-label").transition() + .attr('transform', function(d) { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')'; + }); + + slices.each(function(d, i) { + var slice = d3.select(this); + + slice + .select(".nv-label text") + .text(function(d, i) { + var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); + return (d.value && percent > labelThreshold) ? getX(d.data) : ''; + }); + + var textBox = slice.select('text').node().getBBox(); + slice.select(".nv-label rect") + .attr("width", textBox.width + 10) + .attr("height", textBox.height + 10) + .attr("transform", function() { + return "translate(" + [textBox.x - 5, textBox.y - 5] + ")"; + }); + }); + } + + + // Computes the angle of an arc, converting from radians to degrees. + function angle(d) { + var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; + return a > 90 ? a - 180 : a; + } + + function arcTween(a) { + if (!donut) a.innerRadius = 0; + var i = d3.interpolate(this._current, a); + this._current = i(0); + return function(t) { + return arc(i(t)); + }; + } + + function tweenPie(b) { + b.innerRadius = 0; + var i = d3.interpolate({startAngle: 0, endAngle: 0}, b); + return function(t) { + return arc(i(t)); + }; + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.values = function(_) { + if (!arguments.length) return getValues; + getValues = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.showLabels = function(_) { + if (!arguments.length) return showLabels; + showLabels = _; + return chart; + }; + + chart.donutLabelsOutside = function(_) { + if (!arguments.length) return donutLabelsOutside; + donutLabelsOutside = _; + return chart; + }; + + chart.donut = function(_) { + if (!arguments.length) return donut; + donut = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.valueFormat = function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.labelThreshold = function(_) { + if (!arguments.length) return labelThreshold; + labelThreshold = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.scatterChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , xPadding = 0 + , yPadding = 0 + , showDistX = false + , showDistY = false + , showLegend = true + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '' + x + '' } + , tooltipY = function(key, x, y) { return '' + y + '' } + //, tooltip = function(key, x, y) { return '

' + key + '

' } + , tooltip = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient('left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background') + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } - wrap.select('.nv-point-paths') - .attr('clip-path', 'url(#nv-points-clip-' + id + ')'); - } + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + //------------------------------------------------------------ - if(vertices.length < 3) { - // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work - vertices.push([x.range()[0] - 2000, y.range()[0] - 2000, null, null]); - vertices.push([x.range()[1] + 2000, y.range()[1] + 2000, null, null]); - vertices.push([x.range()[0] - 2000, y.range()[0] + 2000, null, null]); - vertices.push([x.range()[1] + 2000, y.range()[1] - 2000, null, null]); - } - var bounds = d3.geom.polygon([ - [-10,-10], - [-10,height + 10], - [width + 10,height + 10], - [width + 10,-10] - ]); + //------------------------------------------------------------ + // Controls - var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { - return { - 'data': bounds.clip(d), - 'series': vertices[i][2], - 'point': vertices[i][3] - } - }); + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + //------------------------------------------------------------ - var pointPaths = wrap.select('.nv-point-paths').selectAll('path') - .data(voronoi); - pointPaths.enter().append('path') - .attr('class', function(d,i) { return 'nv-path-'+i; }); - pointPaths.exit().remove(); - pointPaths - .attr('d', function(d) { return 'M' + d.data.join('L') + 'Z'; }); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - pointPaths - .on('click', function(d) { - if (needsUpdate) return 0; - var series = data[d.series], - point = series.values[d.point]; - dispatch.elementClick({ - point: point, - series: series, - pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], - seriesIndex: d.series, - pointIndex: d.point - }); - }) - .on('mouseover', function(d) { - if (needsUpdate) return 0; - var series = data[d.series], - point = series.values[d.point]; + //------------------------------------------------------------ + // Main Chart Component(s) - dispatch.elementMouseover({ - point: point, - series: series, - pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], - seriesIndex: d.series, - pointIndex: d.point - }); - }) - .on('mouseout', function(d, i) { - if (needsUpdate) return 0; - var series = data[d.series], - point = series.values[d.point]; + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + + //Adjust for x and y padding + if (xPadding) { + var xRange = x.domain()[1] - x.domain()[0]; + x.domain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]); + } + + if (yPadding) { + var yRange = y.domain()[1] - y.domain()[0]; + y.domain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + + + yAxis + .scale(y) + .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(-' + distY.size() + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .call(scatter); + + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } - dispatch.elementMouseout({ - point: point, - series: series, - seriesIndex: d.series, - pointIndex: d.point - }); - }); + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); } else { - /* - // bring data in form needed for click handlers - var dataWithPoints = vertices.map(function(d, i) { - return { - 'data': d, - 'series': vertices[i][2], - 'point': vertices[i][3] - } - }); - */ + pauseFisheye = false; + } - // add event handlers to points instead voronoi paths - wrap.select('.nv-groups').selectAll('.nv-group') - .selectAll('.nv-point') - //.data(dataWithPoints) - //.style('pointer-events', 'auto') // recativate events, disabled by css - .on('click', function(d,i) { - //nv.log('test', d, i); - if (needsUpdate) return 0; - var series = data[d.series], - point = series.values[i]; + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } - dispatch.elementClick({ - point: point, - series: series, - pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], - seriesIndex: d.series, - pointIndex: i - }); - }) - .on('mouseover', function(d,i) { - if (needsUpdate) return 0; - var series = data[d.series], - point = series.values[i]; + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); - dispatch.elementMouseover({ - point: point, - series: series, - pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], - seriesIndex: d.series, - pointIndex: i - }); - }) - .on('mouseout', function(d,i) { - if (needsUpdate) return 0; - var series = data[d.series], - point = series.values[i]; + chart(selection); + }); - dispatch.elementMouseout({ - point: point, - series: series, - seriesIndex: d.series, - pointIndex: i - }); - }); - } + /* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + chart(selection); + }); - needsUpdate = false; - } + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + chart(selection); + }); + */ - needsUpdate = true; + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', e.pos[1] - availableHeight); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - d3.transition(groups.exit()) - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6) - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .classed('hover', function(d) { return d.hover }); - d3.transition(groups) - .style('fill', function(d,i) { return color(d, i) }) - .style('stroke', function(d,i) { return color(d, i) }) - .style('stroke-opacity', 1) - .style('fill-opacity', .5); + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); - if (onlyCircles) { + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { - var points = groups.selectAll('circle.nv-point') - .data(function(d) { return d.values }); - points.enter().append('circle') - .attr('cx', function(d,i) { return x0(getX(d,i)) }) - .attr('cy', function(d,i) { return y0(getY(d,i)) }) - .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); - points.exit().remove(); - d3.transition(groups.exit().selectAll('path.nv-point')) - .attr('cx', function(d,i) { return x(getX(d,i)) }) - .attr('cy', function(d,i) { return y(getY(d,i)) }) - .remove(); - points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); - d3.transition(points) - .attr('cx', function(d,i) { return x(getX(d,i)) }) - .attr('cy', function(d,i) { return y(getY(d,i)) }) - .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); - } else { + state.disabled = e.disabled; + } - var points = groups.selectAll('path.nv-point') - .data(function(d) { return d.values }); - points.enter().append('path') - .attr('transform', function(d,i) { - return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')' - }) - .attr('d', - d3.svg.symbol() - .type(getShape) - .size(function(d,i) { return z(getSize(d,i)) }) - ); - points.exit().remove(); - d3.transition(groups.exit().selectAll('path.nv-point')) - .attr('transform', function(d,i) { - return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' - }) - .remove(); - points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); - d3.transition(points) - .attr('transform', function(d,i) { - //nv.log(d,i,getX(d,i), x(getX(d,i))); - return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' - }) - .attr('d', - d3.svg.symbol() - .type(getShape) - .size(function(d,i) { return z(getSize(d,i)) }) - ); - } + selection.call(chart); + }); + //============================================================ - // Delay updating the invisible interactive layer for smoother animation - clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer - timeoutID = setTimeout(updateInteractiveLayer, 300); - //updateInteractiveLayer(); //store old scales for use in transitions on update x0 = x.copy(); y0 = y.copy(); - z0 = z.copy(); + }); @@ -8673,16 +10639,16 @@ nv.models.scatter = function() { // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - dispatch.on('elementMouseover.point', function(d) { - if (interactive) - d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) - .classed('hover', true); - }); + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); - dispatch.on('elementMouseout.point', function(d) { - if (interactive) - d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) - .classed('hover', false); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); }); //============================================================ @@ -8692,25 +10658,17 @@ nv.models.scatter = function() { // Expose Public Variables //------------------------------------------------------------ + // expose chart's sub-components chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; - chart.x = function(_) { - if (!arguments.length) return getX; - getX = d3.functor(_); - return chart; - }; - - chart.y = function(_) { - if (!arguments.length) return getY; - getY = d3.functor(_); - return chart; - }; - - chart.size = function(_) { - if (!arguments.length) return getSize; - getSize = d3.functor(_); - return chart; - }; + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); chart.margin = function(_) { if (!arguments.length) return margin; @@ -8733,138 +10691,90 @@ nv.models.scatter = function() { return chart; }; - chart.xScale = function(_) { - if (!arguments.length) return x; - x = _; - return chart; - }; - - chart.yScale = function(_) { - if (!arguments.length) return y; - y = _; - return chart; - }; - - chart.zScale = function(_) { - if (!arguments.length) return z; - z = _; - return chart; - }; - - chart.xDomain = function(_) { - if (!arguments.length) return xDomain; - xDomain = _; - return chart; - }; - - chart.yDomain = function(_) { - if (!arguments.length) return yDomain; - yDomain = _; - return chart; - }; - - chart.sizeDomain = function(_) { - if (!arguments.length) return sizeDomain; - sizeDomain = _; - return chart; - }; - - chart.sizeRange = function(_) { - if (!arguments.length) return sizeRange; - sizeRange = _; - return chart; - }; - - chart.forceX = function(_) { - if (!arguments.length) return forceX; - forceX = _; - return chart; - }; - - chart.forceY = function(_) { - if (!arguments.length) return forceY; - forceY = _; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); return chart; }; - chart.forceSize = function(_) { - if (!arguments.length) return forceSize; - forceSize = _; + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; return chart; }; - chart.interactive = function(_) { - if (!arguments.length) return interactive; - interactive = _; + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; return chart; }; - chart.pointActive = function(_) { - if (!arguments.length) return pointActive; - pointActive = _; + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; return chart; }; - chart.padData = function(_) { - if (!arguments.length) return padData; - padData = _; + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; return chart; }; - chart.clipEdge = function(_) { - if (!arguments.length) return clipEdge; - clipEdge = _; + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; return chart; }; - chart.clipVoronoi= function(_) { - if (!arguments.length) return clipVoronoi; - clipVoronoi = _; + chart.xPadding = function(_) { + if (!arguments.length) return xPadding; + xPadding = _; return chart; }; - chart.useVoronoi= function(_) { - if (!arguments.length) return useVoronoi; - useVoronoi = _; - if (useVoronoi === false) { - clipVoronoi = false; - } + chart.yPadding = function(_) { + if (!arguments.length) return yPadding; + yPadding = _; return chart; }; - chart.clipRadius = function(_) { - if (!arguments.length) return clipRadius; - clipRadius = _; + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; return chart; }; - chart.shape = function(_) { - if (!arguments.length) return getShape; - getShape = _; + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; return chart; }; - chart.onlyCircles = function(_) { - if (!arguments.length) return onlyCircles; - onlyCircles = _; + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; return chart; }; - chart.id = function(_) { - if (!arguments.length) return id; - id = _; + chart.state = function(_) { + if (!arguments.length) return state; + state = _; return chart; }; - chart.singlePoint = function(_) { - if (!arguments.length) return singlePoint; - singlePoint = _; + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; return chart; }; @@ -8874,61 +10784,41 @@ nv.models.scatter = function() { return chart; } -nv.models.scatterChart = function() { +nv.models.scatter = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var scatter = nv.models.scatter() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - , distX = nv.models.distribution() - , distY = nv.models.distribution() - ; - - var margin = {top: 30, right: 20, bottom: 50, left: 75} - , width = null - , height = null - , color = nv.utils.defaultColor() - , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() - , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() - , xPadding = 0 - , yPadding = 0 - , showDistX = false - , showDistY = false - , showLegend = true - , showControls = !!d3.fisheye - , fisheye = 0 - , pauseFisheye = false - , tooltips = true - , tooltipX = function(key, x, y) { return '' + x + '' } - , tooltipY = function(key, x, y) { return '' + y + '' } - //, tooltip = function(key, x, y) { return '

' + key + '

' } - , tooltip = null - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') - , noData = "No Data Available." - ; - - scatter - .xScale(x) - .yScale(y) - ; - xAxis - .orient('bottom') - .tickPadding(10) - ; - yAxis - .orient('left') - .tickPadding(10) - ; - distX - .axis('x') - ; - distY - .axis('y') + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // chooses color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , x = d3.scale.linear() + , y = d3.scale.linear() + , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area + , getX = function(d) { return d.x } // accessor to get the x value + , getY = function(d) { return d.y } // accessor to get the y value + , getSize = function(d) { return d.size || 1} // accessor to get the point size + , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape + , onlyCircles = true // Set to false to use shapes + , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , forceY = [] // List of numbers to Force into the Y scale + , forceSize = [] // List of numbers to Force into the Size scale + , interactive = true // If true, plots a voronoi overlay for advanced point interection + , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = false // if true, masks points within x and y scale + , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance + , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips + , xDomain = null // Override x domain (skips the calculation from data) + , yDomain = null // Override y domain + , sizeDomain = null // Override point size domain + , sizeRange = null + , singlePoint = false + , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout') + , useVoronoi = true ; //============================================================ @@ -8938,361 +10828,367 @@ nv.models.scatterChart = function() { // Private Variables //------------------------------------------------------------ - var state = {}, - x0, y0; - - var showTooltip = function(e, offsetElement) { - //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) - - var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), - top = e.pos[1] + ( offsetElement.offsetTop || 0), - leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), - topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), - leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), - topY = e.pos[1] + ( offsetElement.offsetTop || 0), - xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), - yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); - - if( tooltipX != null ) - nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); - if( tooltipY != null ) - nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); - if( tooltip != null ) - nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); - }; - - var controlsData = [ - { key: 'Magnify', disabled: true } - ]; - - //============================================================ - - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - - chart.update = function() { chart(selection) }; - chart.container = this; - - - //------------------------------------------------------------ - // Display noData message if there's nothing to show. - - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); - - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); - - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); - - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Setup Scales - - x0 = x0 || x; - y0 = y0 || y; - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Setup containers and skeleton of chart - - var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g') - - // background for pointer events - gEnter.append('rect').attr('class', 'nvd3 nv-background') - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); - gEnter.append('g').attr('class', 'nv-distWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Legend + var x0, y0, z0 // used to store previous scales + , timeoutID + , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips + ; - if (showLegend) { - legend.width( availableWidth / 2 ); + //============================================================ - wrap.select('.nv-legendWrap') - .datum(data) - .call(legend); - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); - } + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); //------------------------------------------------------------ + // Setup Scales + // 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) { + return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } + }) + }) + ); - //------------------------------------------------------------ - // Controls + x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x }).concat(forceX))) - if (showControls) { - controls.width(180).color(['#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); - //------------------------------------------------------------ + y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) + .range([availableHeight, 0]); + z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) + .range(sizeRange || [16, 256]); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); - //------------------------------------------------------------ - // Main Chart Component(s) - scatter - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })) + x0 = x0 || x; + y0 = y0 || y; + z0 = z0 || z; - wrap.select('.nv-scatterWrap') - .datum(data.filter(function(d) { return !d.disabled })) - .call(scatter); + //------------------------------------------------------------ - //Adjust for x and y padding - if (xPadding) { - var xRange = x.domain()[1] - x.domain()[0]; - x.domain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]); - } + //------------------------------------------------------------ + // Setup containers and skeleton of chart - if (yPadding) { - var yRange = y.domain()[1] - y.domain()[0]; - y.domain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]); - } + 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 + (singlePoint ? ' nv-single-point' : '')); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - //------------------------------------------------------------ + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-point-paths'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); //------------------------------------------------------------ - // Setup Axes - xAxis - .scale(x) - .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 ) - .tickSize( -availableHeight , 0); - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')') - .call(xAxis); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - yAxis - .scale(y) - .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 ) - .tickSize( -availableWidth, 0); + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - g.select('.nv-y.nv-axis') - .call(yAxis); + function updateInteractiveLayer() { - if (showDistX) { - distX - .getData(scatter.x()) - .scale(x) - .width(availableWidth) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - gEnter.select('.nv-distWrap').append('g') - .attr('class', 'nv-distributionX'); - g.select('.nv-distributionX') - .attr('transform', 'translate(0,' + y.range()[0] + ')') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distX); - } + if (!interactive) return false; - if (showDistY) { - distY - .getData(scatter.y()) - .scale(y) - .width(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - gEnter.select('.nv-distWrap').append('g') - .attr('class', 'nv-distributionY'); - g.select('.nv-distributionY') - .attr('transform', 'translate(-' + distY.size() + ',0)') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distY); - } + var eventElements; - //------------------------------------------------------------ + var vertices = d3.merge(data.map(function(group, groupIndex) { + return group.values + .map(function(point, pointIndex) { + // *Adding noise to make duplicates very unlikely + // **Injecting series and point index for reference + return [x(getX(point,pointIndex)) * (Math.random() / 1e12 + 1) , y(getY(point,pointIndex)) * (Math.random() / 1e12 + 1), groupIndex, pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates + }) + .filter(function(pointArray, pointIndex) { + return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! + }) + }) + ); + //inject series and point index for reference into voronoi + if (useVoronoi === true) { - if (d3.fisheye) { - g.select('.nv-background') - .attr('width', availableWidth) - .attr('height', availableHeight); + if (clipVoronoi) { + var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips') + .data([id]) + .enter(); - g.select('.nv-background').on('mousemove', updateFisheye); - g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); - scatter.dispatch.on('elementClick.freezeFisheye', function() { - pauseFisheye = !pauseFisheye; - }); - } + pointClipsEnter.append('clipPath') + .attr('class', 'nv-point-clips') + .attr('id', 'nv-points-clip-' + id); + var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle') + .data(vertices); + pointClips.enter().append('circle') + .attr('r', clipRadius); + pointClips.exit().remove(); + pointClips + .attr('cx', function(d) { return d[0] }) + .attr('cy', function(d) { return d[1] }); - function updateFisheye() { - if (pauseFisheye) { - g.select('.nv-point-paths').style('pointer-events', 'all'); - return false; - } + wrap.select('.nv-point-paths') + .attr('clip-path', 'url(#nv-points-clip-' + id + ')'); + } - g.select('.nv-point-paths').style('pointer-events', 'none' ); - var mouse = d3.mouse(this); - x.distortion(fisheye).focus(mouse[0]); - y.distortion(fisheye).focus(mouse[1]); + if(vertices.length < 3) { + // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work + vertices.push([x.range()[0] - 2000, y.range()[0] - 2000, null, null]); + vertices.push([x.range()[1] + 2000, y.range()[1] + 2000, null, null]); + vertices.push([x.range()[0] - 2000, y.range()[0] + 2000, null, null]); + vertices.push([x.range()[1] + 2000, y.range()[1] - 2000, null, null]); + } - g.select('.nv-scatterWrap') - .call(scatter); + var bounds = d3.geom.polygon([ + [-10,-10], + [-10,height + 10], + [width + 10,height + 10], + [width + 10,-10] + ]); - g.select('.nv-x.nv-axis').call(xAxis); - g.select('.nv-y.nv-axis').call(yAxis); - g.select('.nv-distributionX') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distX); - g.select('.nv-distributionY') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distY); - } + var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { + return { + 'data': bounds.clip(d), + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + var pointPaths = wrap.select('.nv-point-paths').selectAll('path') + .data(voronoi); + pointPaths.enter().append('path') + .attr('class', function(d,i) { return 'nv-path-'+i; }); + pointPaths.exit().remove(); + pointPaths + .attr('d', function(d) { return 'M' + d.data.join('L') + 'Z'; }); - controls.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; + pointPaths + .on('click', function(d) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; - fisheye = d.disabled ? 0 : 2.5; - g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); - g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }) + .on('mouseover', function(d) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; - if (d.disabled) { - x.distortion(fisheye).focus(0); - y.distortion(fisheye).focus(0); + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }) + .on('mouseout', function(d, i) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; - g.select('.nv-scatterWrap').call(scatter); - g.select('.nv-x.nv-axis').call(xAxis); - g.select('.nv-y.nv-axis').call(yAxis); - } else { - pauseFisheye = false; - } + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: d.point + }); + }); - chart(selection); - }); - legend.dispatch.on('legendClick', function(d,i, that) { - d.disabled = !d.disabled; + } else { + /* + // bring data in form needed for click handlers + var dataWithPoints = vertices.map(function(d, i) { + return { + 'data': d, + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + */ - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - wrap.selectAll('.nv-series').classed('disabled', false); - return d; - }); - } + // add event handlers to points instead voronoi paths + wrap.select('.nv-groups').selectAll('.nv-group') + .selectAll('.nv-point') + //.data(dataWithPoints) + //.style('pointer-events', 'auto') // recativate events, disabled by css + .on('click', function(d,i) { + //nv.log('test', d, i); + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[i]; - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseover', function(d,i) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[i]; - chart(selection); - }); + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseout', function(d,i) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[i]; - /* - legend.dispatch.on('legendMouseover', function(d, i) { - d.hover = true; - chart(selection); - }); + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: i + }); + }); + } - legend.dispatch.on('legendMouseout', function(d, i) { - d.hover = false; - chart(selection); - }); - */ + needsUpdate = false; + } - scatter.dispatch.on('elementMouseover.tooltip', function(e) { - d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) - .attr('y1', e.pos[1] - availableHeight); - d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) - .attr('x2', e.pos[0] + distX.size()); + needsUpdate = true; - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; - dispatch.tooltipShow(e); - }); + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + d3.transition(groups) + .style('fill', function(d,i) { return color(d, i) }) + .style('stroke', function(d,i) { return color(d, i) }) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); - dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e, that.parentNode); - }); - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { + if (onlyCircles) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); + var points = groups.selectAll('circle.nv-point') + .data(function(d) { return d.values }); + points.enter().append('circle') + .attr('cx', function(d,i) { return x0(getX(d,i)) }) + .attr('cy', function(d,i) { return y0(getY(d,i)) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + points.exit().remove(); + d3.transition(groups.exit().selectAll('path.nv-point')) + .attr('cx', function(d,i) { return x(getX(d,i)) }) + .attr('cy', function(d,i) { return y(getY(d,i)) }) + .remove(); + points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); + d3.transition(points) + .attr('cx', function(d,i) { return x(getX(d,i)) }) + .attr('cy', function(d,i) { return y(getY(d,i)) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); - state.disabled = e.disabled; - } + } else { - selection.call(chart); - }); + var points = groups.selectAll('path.nv-point') + .data(function(d) { return d.values }); + points.enter().append('path') + .attr('transform', function(d,i) { + return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + points.exit().remove(); + d3.transition(groups.exit().selectAll('path.nv-point')) + .attr('transform', function(d,i) { + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .remove(); + points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); + d3.transition(points) + .attr('transform', function(d,i) { + //nv.log(d,i,getX(d,i), x(getX(d,i))); + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + } - //============================================================ + // Delay updating the invisible interactive layer for smoother animation + clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer + timeoutID = setTimeout(updateInteractiveLayer, 300); + //updateInteractiveLayer(); //store old scales for use in transitions on update x0 = x.copy(); y0 = y.copy(); - + z0 = z.copy(); }); @@ -9304,16 +11200,16 @@ nv.models.scatterChart = function() { // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - scatter.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - - d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) - .attr('y1', 0); - d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) - .attr('x2', distY.size()); + dispatch.on('elementMouseover.point', function(d) { + if (interactive) + d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) + .classed('hover', true); }); - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); + + dispatch.on('elementMouseout.point', function(d) { + if (interactive) + d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) + .classed('hover', false); }); //============================================================ @@ -9323,17 +11219,25 @@ nv.models.scatterChart = function() { // Expose Public Variables //------------------------------------------------------------ - // expose chart's sub-components chart.dispatch = dispatch; - chart.scatter = scatter; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.distX = distX; - chart.distY = distY; - d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return getSize; + getSize = d3.functor(_); + return chart; + }; chart.margin = function(_) { if (!arguments.length) return margin; @@ -9356,90 +11260,138 @@ nv.models.scatterChart = function() { return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - legend.color(color); - distX.color(color); - distY.color(color); + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; return chart; }; - chart.showDistX = function(_) { - if (!arguments.length) return showDistX; - showDistX = _; + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; return chart; }; - chart.showDistY = function(_) { - if (!arguments.length) return showDistY; - showDistY = _; + chart.zScale = function(_) { + if (!arguments.length) return z; + z = _; return chart; }; - chart.showControls = function(_) { - if (!arguments.length) return showControls; - showControls = _; + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; return chart; }; - chart.showLegend = function(_) { - if (!arguments.length) return showLegend; - showLegend = _; + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; return chart; }; - chart.fisheye = function(_) { - if (!arguments.length) return fisheye; - fisheye = _; + chart.sizeDomain = function(_) { + if (!arguments.length) return sizeDomain; + sizeDomain = _; return chart; }; - chart.xPadding = function(_) { - if (!arguments.length) return xPadding; - xPadding = _; + chart.sizeRange = function(_) { + if (!arguments.length) return sizeRange; + sizeRange = _; return chart; }; - chart.yPadding = function(_) { - if (!arguments.length) return yPadding; - yPadding = _; + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; return chart; }; - chart.tooltips = function(_) { - if (!arguments.length) return tooltips; - tooltips = _; + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; return chart; }; - chart.tooltipContent = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; + chart.forceSize = function(_) { + if (!arguments.length) return forceSize; + forceSize = _; return chart; }; - chart.tooltipXContent = function(_) { - if (!arguments.length) return tooltipX; - tooltipX = _; + chart.interactive = function(_) { + if (!arguments.length) return interactive; + interactive = _; return chart; }; - chart.tooltipYContent = function(_) { - if (!arguments.length) return tooltipY; - tooltipY = _; + chart.pointActive = function(_) { + if (!arguments.length) return pointActive; + pointActive = _; return chart; }; - chart.state = function(_) { - if (!arguments.length) return state; - state = _; + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; return chart; }; - chart.noData = function(_) { - if (!arguments.length) return noData; - noData = _; + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.clipVoronoi= function(_) { + if (!arguments.length) return clipVoronoi; + clipVoronoi = _; + return chart; + }; + + chart.useVoronoi= function(_) { + if (!arguments.length) return useVoronoi; + useVoronoi = _; + if (useVoronoi === false) { + clipVoronoi = false; + } + return chart; + }; + + chart.clipRadius = function(_) { + if (!arguments.length) return clipRadius; + clipRadius = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.shape = function(_) { + if (!arguments.length) return getShape; + getShape = _; + return chart; + }; + + chart.onlyCircles = function(_) { + if (!arguments.length) return onlyCircles; + onlyCircles = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.singlePoint = function(_) { + if (!arguments.length) return singlePoint; + singlePoint = _; return chart; }; @@ -10496,661 +12448,663 @@ nv.models.sparklinePlus = function() { return chart; } -nv.models.stackedArea = function() { +nv.models.stackedAreaChart = function() { //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , color = nv.utils.defaultColor() // a function that computes the color - , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one - , getX = function(d) { return d.x } // accessor to get the x value from a data point - , getY = function(d) { return d.y } // accessor to get the y value from a data point - , style = 'stack' - , offset = 'zero' - , order = 'default' - , interpolate = 'linear' // controls the line interpolation - , clipEdge = false // if true, masks lines within x and y scale + var stacked = nv.models.stackedArea() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 25, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() // a function that takes in d, i and returns color + , showControls = true + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

' + key + '

' + + '

' + y + ' on ' + x + '

' + } , x //can be accessed via chart.xScale() , y //can be accessed via chart.yScale() - , scatter = nv.models.scatter() - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout') + , yAxisTickFormat = d3.format(',.2f') + , state = { style: stacked.style() } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') ; - scatter - .size(2.2) // default size - .sizeDomain([2.2]) // all the same size by default + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + stacked.scatter + .pointActive(function(d) { + //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100)); + return !!Math.round(stacked.y()(d) * 100); + }) ; - /************************************ - * offset: - * 'wiggle' (stream) - * 'zero' (stacked) - * 'expand' (normalize to 100%) - * 'silhouette' (simple centered) - * - * order: - * 'inside-out' (stream) - * 'default' (input order) - ************************************/ + //============================================================ + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + //------------------------------------------------------------ - function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom, - container = d3.select(this); //------------------------------------------------------------ // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); + x = stacked.xScale(); + y = stacked.yScale(); //------------------------------------------------------------ - // Injecting point index into each point because d3.layout.stack().out does not give index - // ***Also storing getY(d,i) as stackedY so that it can be set to 0 if series is disabled - data = data.map(function(aseries, i) { - aseries.values = aseries.values.map(function(d, j) { - d.index = j; - d.stackedY = aseries.disabled ? 0 : getY(d,j); - return d; - }) - return aseries; - }); - - - data = d3.layout.stack() - .order(order) - .offset(offset) - .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion - .x(getX) - .y(function(d) { return d.stackedY }) - .out(function(d, y0, y) { - d.display = { - y: y, - y0: y0 - }; - }) - (data); - - //------------------------------------------------------------ // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); + 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('g').attr('class', 'nv-areaWrap'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + 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'); //------------------------------------------------------------ - scatter - .width(availableWidth) - .height(availableHeight) - .x(getX) - .y(function(d) { return d.display.y + d.display.y0 }) - .forceY([0]) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - - - var scatterWrap = g.select('.nv-scatterWrap') - .datum(data.filter(function(d) { return !d.disabled })) - - //d3.transition(scatterWrap).call(scatter); - scatterWrap.call(scatter); - + //------------------------------------------------------------ + // Legend + if (showLegend) { + legend + .width( availableWidth * 2 / 3 ); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + ( availableWidth * 1 / 3 ) + ',' + (-margin.top) +')'); + } - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + //------------------------------------------------------------ - g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + //------------------------------------------------------------ + // Controls + if (showControls) { + var controlsData = [ + { key: 'Stacked', disabled: stacked.offset() != 'zero' }, + { key: 'Stream', disabled: stacked.offset() != 'wiggle' }, + { key: 'Expanded', disabled: stacked.offset() != 'expand' } + ]; + controls + .width( Math.min(280, availableWidth * 1 / 3) ) + .color(['#444', '#444', '#444']); - var area = d3.svg.area() - .x(function(d,i) { return x(getX(d,i)) }) - .y0(function(d) { return y(d.display.y0) }) - .y1(function(d) { return y(d.display.y + d.display.y0) }) - .interpolate(interpolate); + g.select('.nv-controlsWrap') + .datum(controlsData) + .call(controls); - var zeroArea = d3.svg.area() - .x(function(d,i) { return x(getX(d,i)) }) - .y0(function(d) { return y(d.display.y0) }) - .y1(function(d) { return y(d.display.y0) }); + if ( margin.top != Math.max(controls.height(), legend.height()) ) { + margin.top = Math.max(controls.height(), legend.height()); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } - var path = g.select('.nv-areaWrap').selectAll('path.nv-area') - .data(function(d) { return d }); - //.data(function(d) { return d }, function(d) { return d.key }); - path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) - .on('mouseover', function(d,i) { - d3.select(this).classed('hover', true); - dispatch.areaMouseover({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: i - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.areaMouseout({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: i - }); - }) - .on('click', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.areaClick({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: i - }); - }) - //d3.transition(path.exit()) - path.exit() - .attr('d', function(d,i) { return zeroArea(d.values,i) }) - .remove(); - path - .style('fill', function(d,i){ return d.color || color(d, i) }) - .style('stroke', function(d,i){ return d.color || color(d, i) }); - //d3.transition(path) - path - .attr('d', function(d,i) { return area(d.values,i) }) + g.select('.nv-controlsWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } - //============================================================ - // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ - scatter.dispatch.on('elementMouseover.area', function(e) { - g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); - }); - scatter.dispatch.on('elementMouseout.area', function(e) { - g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); - }); - - //============================================================ - - }); - - - return chart; - } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - scatter.dispatch.on('elementClick.area', function(e) { - dispatch.areaClick(e); - }) - scatter.dispatch.on('elementMouseover.tooltip', function(e) { - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], - dispatch.tooltipShow(e); - }); - scatter.dispatch.on('elementMouseout.tooltip', function(e) { - dispatch.tooltipHide(e); - }); + //------------------------------------------------------------ + // Main Chart Component(s) - //============================================================ + stacked + .width(availableWidth) + .height(availableHeight) + var stackedWrap = g.select('.nv-stackedWrap') + .datum(data); + //d3.transition(stackedWrap).call(stacked); + stackedWrap.call(stacked); - //============================================================ - // Global getters and setters - //------------------------------------------------------------ + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.scatter = scatter; - d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); + //------------------------------------------------------------ + // Setup Axes - chart.x = function(_) { - if (!arguments.length) return getX; - getX = d3.functor(_); - return chart; - }; + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); - chart.y = function(_) { - if (!arguments.length) return getY; - getY = d3.functor(_); - return chart; - } + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis') + .transition().duration(0) + .call(xAxis); - chart.margin = function(_) { - if (!arguments.length) return margin; - 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; - return chart; - }; + yAxis + .scale(y) + .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36) + .tickSize(-availableWidth, 0) + .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat); - chart.width = function(_) { - if (!arguments.length) return width; - width = _; - return chart; - }; + //d3.transition(g.select('.nv-y.nv-axis')) + g.select('.nv-y.nv-axis') + .transition().duration(0) + .call(yAxis); - chart.height = function(_) { - if (!arguments.length) return height; - height = _; - return chart; - }; + //------------------------------------------------------------ - chart.clipEdge = function(_) { - if (!arguments.length) return clipEdge; - clipEdge = _; - return chart; - }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - return chart; - }; + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - chart.offset = function(_) { - if (!arguments.length) return offset; - offset = _; - return chart; - }; + stacked.dispatch.on('areaClick.toggle', function(e) { + if (data.filter(function(d) { return !d.disabled }).length === 1) + data = data.map(function(d) { + d.disabled = false; + return d + }); + else + data = data.map(function(d,i) { + d.disabled = (i != e.seriesIndex); + return d + }); - chart.order = function(_) { - if (!arguments.length) return order; - order = _; - return chart; - }; + //selection.transition().call(chart); + chart(selection); + }); - //shortcut for offset + order - chart.style = function(_) { - if (!arguments.length) return style; - style = _; + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; - switch (style) { - case 'stack': - chart.offset('zero'); - chart.order('default'); - break; - case 'stream': - chart.offset('wiggle'); - chart.order('inside-out'); - break; - case 'stream-center': - chart.offset('silhouette'); - chart.order('inside-out'); - break; - case 'expand': - chart.offset('expand'); - chart.order('default'); - break; - } + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + return d; + }); + } - return chart; - }; + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); - chart.interpolate = function(_) { - if (!arguments.length) return interpolate; - interpolate = _; - return interpolate; - - }; - - //============================================================ + //selection.transition().call(chart); + chart(selection); + }); + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; - return chart; -} + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; -nv.models.stackedAreaChart = function() { + switch (d.key) { + case 'Stacked': + stacked.style('stack'); + break; + case 'Stream': + stacked.style('stream'); + break; + case 'Expanded': + stacked.style('expand'); + break; + } - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + state.style = stacked.style(); + dispatch.stateChange(state); - var stacked = nv.models.stackedArea() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - ; + //selection.transition().call(chart); + chart(selection); + }); - var margin = {top: 30, right: 25, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.defaultColor() // a function that takes in d, i and returns color - , showControls = true - , showLegend = true - , tooltips = true - , tooltip = function(key, x, y, e, graph) { - return '

' + key + '

' + - '

' + y + ' on ' + x + '

' - } - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , yAxisTickFormat = d3.format(',.2f') - , state = { style: stacked.style() } - , noData = 'No Data Available.' - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') - ; + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); - xAxis - .orient('bottom') - .tickPadding(7) - ; - yAxis - .orient('left') - ; - stacked.scatter - .pointActive(function(d) { - //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100)); - return !!Math.round(stacked.y()(d) * 100); - }) - ; + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { - //============================================================ + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + state.disabled = e.disabled; + } - //============================================================ - // Private Variables - //------------------------------------------------------------ + if (typeof e.style !== 'undefined') { + stacked.style(e.style); + } - var showTooltip = function(e, offsetElement) { - var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), - top = e.pos[1] + ( offsetElement.offsetTop || 0), - x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)), - y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)), - content = tooltip(e.series.key, x, y, e, chart); + selection.call(chart); + }); - nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); - }; + }); - //============================================================ + return chart; + } - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - chart.update = function() { chart(selection) }; - chart.container = this; + stacked.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + /* + if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + */ - //------------------------------------------------------------ - // Display No Data message if there's nothing to show. + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); + stacked.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); + //============================================================ - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.stacked = stacked; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate'); - //------------------------------------------------------------ - // Setup Scales + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; + return chart; + }; - x = stacked.xScale(); - y = stacked.yScale(); + chart.width = function(_) { + if (!arguments.length) return getWidth; + width = _; + return chart; + }; - //------------------------------------------------------------ + chart.height = function(_) { + if (!arguments.length) return getHeight; + height = _; + return chart; + }; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + stacked.color(color); + return chart; + }; - //------------------------------------------------------------ - // Setup containers and skeleton of chart + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return 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'); + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; - 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'); + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; - //------------------------------------------------------------ + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; - //------------------------------------------------------------ - // Legend + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; - if (showLegend) { - legend - .width( availableWidth * 2 / 3 ); + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; - g.select('.nv-legendWrap') - .datum(data) - .call(legend); + yAxis.setTickFormat = yAxis.tickFormat; + yAxis.tickFormat = function(_) { + if (!arguments.length) return yAxisTickFormat; + yAxisTickFormat = _; + return yAxis; + }; - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } + //============================================================ - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + ( availableWidth * 1 / 3 ) + ',' + (-margin.top) +')'); - } + return chart; +} - //------------------------------------------------------------ +nv.models.stackedArea = function() { + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - //------------------------------------------------------------ - // Controls + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that computes the color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , style = 'stack' + , offset = 'zero' + , order = 'default' + , interpolate = 'linear' // controls the line interpolation + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , scatter = nv.models.scatter() + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout') + ; - if (showControls) { - var controlsData = [ - { key: 'Stacked', disabled: stacked.offset() != 'zero' }, - { key: 'Stream', disabled: stacked.offset() != 'wiggle' }, - { key: 'Expanded', disabled: stacked.offset() != 'expand' } - ]; + scatter + .size(2.2) // default size + .sizeDomain([2.2]) // all the same size by default + ; - controls - .width( Math.min(280, availableWidth * 1 / 3) ) - .color(['#444', '#444', '#444']); + /************************************ + * offset: + * 'wiggle' (stream) + * 'zero' (stacked) + * 'expand' (normalize to 100%) + * 'silhouette' (simple centered) + * + * order: + * 'inside-out' (stream) + * 'default' (input order) + ************************************/ - g.select('.nv-controlsWrap') - .datum(controlsData) - .call(controls); + //============================================================ - if ( margin.top != Math.max(controls.height(), legend.height()) ) { - margin.top = Math.max(controls.height(), legend.height()); - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - } + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + //------------------------------------------------------------ + // Setup Scales - g.select('.nv-controlsWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')'); - } + x = scatter.xScale(); + y = scatter.yScale(); //------------------------------------------------------------ - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - + // Injecting point index into each point because d3.layout.stack().out does not give index + // ***Also storing getY(d,i) as stackedY so that it can be set to 0 if series is disabled + data = data.map(function(aseries, i) { + aseries.values = aseries.values.map(function(d, j) { + d.index = j; + d.stackedY = aseries.disabled ? 0 : getY(d,j); + return d; + }) + return aseries; + }); - //------------------------------------------------------------ - // Main Chart Component(s) - stacked - .width(availableWidth) - .height(availableHeight) + data = d3.layout.stack() + .order(order) + .offset(offset) + .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion + .x(getX) + .y(function(d) { return d.stackedY }) + .out(function(d, y0, y) { + d.display = { + y: y, + y0: y0 + }; + }) + (data); - var stackedWrap = g.select('.nv-stackedWrap') - .datum(data); - //d3.transition(stackedWrap).call(stacked); - stackedWrap.call(stacked); //------------------------------------------------------------ + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - //------------------------------------------------------------ - // Setup Axes - - xAxis - .scale(x) - .ticks( availableWidth / 100 ) - .tickSize( -availableHeight, 0); + gEnter.append('g').attr('class', 'nv-areaWrap'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - //d3.transition(g.select('.nv-x.nv-axis')) - g.select('.nv-x.nv-axis') - .transition().duration(0) - .call(xAxis); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - yAxis - .scale(y) - .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36) - .tickSize(-availableWidth, 0) - .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat); + //------------------------------------------------------------ - //d3.transition(g.select('.nv-y.nv-axis')) - g.select('.nv-y.nv-axis') - .transition().duration(0) - .call(yAxis); - //------------------------------------------------------------ + scatter + .width(availableWidth) + .height(availableHeight) + .x(getX) + .y(function(d) { return d.display.y + d.display.y0 }) + .forceY([0]) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + var scatterWrap = g.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) - stacked.dispatch.on('areaClick.toggle', function(e) { - if (data.filter(function(d) { return !d.disabled }).length === 1) - data = data.map(function(d) { - d.disabled = false; - return d - }); - else - data = data.map(function(d,i) { - d.disabled = (i != e.seriesIndex); - return d - }); + //d3.transition(scatterWrap).call(scatter); + scatterWrap.call(scatter); - //selection.transition().call(chart); - chart(selection); - }); - legend.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - return d; - }); - } - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - //selection.transition().call(chart); - chart(selection); - }); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); - controls.dispatch.on('legendClick', function(d,i) { - if (!d.disabled) return; + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; - }); - d.disabled = false; + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - switch (d.key) { - case 'Stacked': - stacked.style('stack'); - break; - case 'Stream': - stacked.style('stream'); - break; - case 'Expanded': - stacked.style('expand'); - break; - } - state.style = stacked.style(); - dispatch.stateChange(state); - //selection.transition().call(chart); - chart(selection); - }); - dispatch.on('tooltipShow', function(e) { - if (tooltips) showTooltip(e, that.parentNode); - }); + var area = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y + d.display.y0) }) + .interpolate(interpolate); - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { + var zeroArea = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y0) }); - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - state.disabled = e.disabled; - } + var path = g.select('.nv-areaWrap').selectAll('path.nv-area') + .data(function(d) { return d }); + //.data(function(d) { return d }, function(d) { return d.key }); + path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.areaMouseover({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaMouseout({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('click', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaClick({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + //d3.transition(path.exit()) + path.exit() + .attr('d', function(d,i) { return zeroArea(d.values,i) }) + .remove(); + path + .style('fill', function(d,i){ return d.color || color(d, i) }) + .style('stroke', function(d,i){ return d.color || color(d, i) }); + //d3.transition(path) + path + .attr('d', function(d,i) { return area(d.values,i) }) - if (typeof e.style !== 'undefined') { - stacked.style(e.style); - } - selection.call(chart); + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseover.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); + }); + scatter.dispatch.on('elementMouseout.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); }); + //============================================================ + }); @@ -11162,44 +13116,40 @@ nv.models.stackedAreaChart = function() { // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ - stacked.dispatch.on('tooltipShow', function(e) { - //disable tooltips when value ~= 0 - //// TODO: consider removing points from voronoi that have 0 value instead of this hack - /* - if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range - setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); - return false; - } - */ - - e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], - dispatch.tooltipShow(e); - }); - - stacked.dispatch.on('tooltipHide', function(e) { - dispatch.tooltipHide(e); + scatter.dispatch.on('elementClick.area', function(e) { + dispatch.areaClick(e); + }) + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); }); - - dispatch.on('tooltipHide', function() { - if (tooltips) nv.tooltip.cleanup(); + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); }); //============================================================ //============================================================ - // Expose Public Variables + // Global getters and setters //------------------------------------------------------------ - // expose chart's sub-components chart.dispatch = dispatch; - chart.stacked = stacked; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; + chart.scatter = scatter; - d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate'); + d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + } chart.margin = function(_) { if (!arguments.length) return margin; @@ -11211,76 +13161,78 @@ nv.models.stackedAreaChart = function() { }; chart.width = function(_) { - if (!arguments.length) return getWidth; + if (!arguments.length) return width; width = _; return chart; }; chart.height = function(_) { - if (!arguments.length) return getHeight; + if (!arguments.length) return height; height = _; return chart; }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - legend.color(color); - stacked.color(color); - return chart; - }; - - chart.showControls = function(_) { - if (!arguments.length) return showControls; - showControls = _; + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; return chart; }; - chart.showLegend = function(_) { - if (!arguments.length) return showLegend; - showLegend = _; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); return chart; }; - chart.tooltip = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; + chart.offset = function(_) { + if (!arguments.length) return offset; + offset = _; return chart; }; - chart.tooltips = function(_) { - if (!arguments.length) return tooltips; - tooltips = _; + chart.order = function(_) { + if (!arguments.length) return order; + order = _; return chart; }; - chart.tooltipContent = function(_) { - if (!arguments.length) return tooltip; - tooltip = _; - return chart; - }; + //shortcut for offset + order + chart.style = function(_) { + if (!arguments.length) return style; + style = _; - chart.state = function(_) { - if (!arguments.length) return state; - state = _; - return chart; - }; + switch (style) { + case 'stack': + chart.offset('zero'); + chart.order('default'); + break; + case 'stream': + chart.offset('wiggle'); + chart.order('inside-out'); + break; + case 'stream-center': + chart.offset('silhouette'); + chart.order('inside-out'); + break; + case 'expand': + chart.offset('expand'); + chart.order('default'); + break; + } - chart.noData = function(_) { - if (!arguments.length) return noData; - noData = _; return chart; }; - yAxis.setTickFormat = yAxis.tickFormat; - yAxis.tickFormat = function(_) { - if (!arguments.length) return yAxisTickFormat; - yAxisTickFormat = _; - return yAxis; + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return interpolate; + }; - + //============================================================ + return chart; } })(); \ No newline at end of file diff --git a/nv.d3.min.js b/nv.d3.min.js index e69de29..632ac70 100644 --- a/nv.d3.min.js +++ b/nv.d3.min.js @@ -0,0 +1,5 @@ +(function(){function b(a,b){return(new Date(b,a+1,0)).getDate()}function c(a,b,c){return function(d,e,f){var g=a(d),h=[];g1)while(g=document.body.scrollWidth?m:m-16,l=window.innerHeight>=document.body.scrollHeight?l:l-16;var r=function(a){var b=q;do isNaN(a.offsetTop)||(b+=a.offsetTop);while(a=a.offsetParent);return b},s=function(a){var b=p;do isNaN(a.offsetLeft)||(b+=a.offsetLeft);while(a=a.offsetParent);return b};switch(d){case"e":p=b[0]-k-e,q=b[1]-j/2;var t=s(h),u=r(h);to?b[0]+e:o-t+p),un+m&&(q=n+m-u+q-j);break;case"w":p=b[0]+e,q=b[1]-j/2,t+k>l&&(p=b[0]-k-e),un+m&&(q=n-j-5);break;case"n":p=b[0]-k/2-5,q=b[1]+e;var t=s(h),u=r(h);tl&&(p=p-k/2+5),u+j>n+m&&(q=n+m-u+q-j);break;case"s":p=b[0]-k/2,q=b[1]-j-e;var t=s(h),u=r(h);tl&&(p=p-k/2+5),n>u&&(q=n)}return h.style.left=p+"px",h.style.top=q+"px",h.style.opacity=1,h.style.position="absolute",h.style.pointerEvents="none",h},b.cleanup=function(){var a=document.getElementsByClassName("nvtooltip"),b=[];while(a.length)b.push(a[0]),a[0].style.transitionDelay="0 !important",a[0].style.opacity=0,a[0].className="nvtooltip-pending-removal";setTimeout(function(){while(b.length){var a=b.pop();a.parentNode.removeChild(a)}},500)}}(),a.utils.windowSize=function(){var a={width:640,height:480};return document.body&&document.body.offsetWidth&&(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight),document.compatMode=="CSS1Compat"&&document.documentElement&&document.documentElement.offsetWidth&&(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight),window.innerWidth&&window.innerHeight&&(a.width=window.innerWidth,a.height=window.innerHeight),a},a.utils.windowResize=function(a){var b=window.onresize;window.onresize=function(c){typeof b=="function"&&b(c),a(c)}},a.utils.getColor=function(b){return arguments.length?Object.prototype.toString.call(b)==="[object Array]"?function(a,c){return a.color||b[c%b.length]}:b:a.utils.defaultColor()},a.utils.defaultColor=function(){var a=d3.scale.category20().range();return function(b,c){return b.color||a[c%a.length]}},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e,f){var g=b(e);return d||(d=c.length),typeof a[g]!="undefined"?typeof a[g]=="function"?a[g]():a[g]:c[--d]}},a.utils.pjax=function(b,c){function d(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})}d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.models.axis=function(){function o(d){return d.each(function(d){var o=d3.select(this),p=o.selectAll("g.nv-wrap.nv-axis").data([d]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),r=q.append("g"),s=p.select("g");m!==null?a.ticks(m):(a.orient()=="top"||a.orient()=="bottom")&&a.ticks(Math.abs(e.range()[1]-e.range()[0])/100),d3.transition(s).call(a),n=n||a.scale();var t=a.tickFormat();t==null&&(t=n.tickFormat());var u=s.selectAll("text.nv-axislabel").data([f||null]);u.exit().remove();switch(a.orient()){case"top":u.enter().append("text").attr("class","nv-axislabel").attr("text-anchor","middle").attr("y",0);var v=e.range().length==2?e.range()[1]:e.range()[e.range().length-1]+(e.range()[1]-e.range()[0]);u.attr("x",v/2);if(g){var w=p.selectAll("g.nv-axisMaxMin").data(e.domain());w.enter().append("g").attr("class","nv-axisMaxMin").append("text"),w.exit().remove(),w.attr("transform",function(a,b){return"translate("+e(a)+",0)"}).select("text").attr("dy","0em").attr("y",-a.tickPadding()).attr("text-anchor","middle").text(function(a,b){var c=t(a);return(""+c).match("NaN")?"":c}),d3.transition(w).attr("transform",function(a,b){return"translate("+e.range()[b]+",0)"})}break;case"bottom":var x=36,y=30,z=s.selectAll("g").select("text");if(i%360){z.each(function(a,b){var c=this.getBBox().width;c>y&&(y=c)});var A=Math.abs(Math.sin(i*Math.PI/180)),x=(A?A*y:y)+30;z.attr("transform",function(a,b,c){return"rotate("+i+" 0,0)"}).attr("text-anchor",i%360>0?"start":"end")}u.enter().append("text").attr("class","nv-axislabel").attr("text-anchor","middle").attr("y",x);var v=e.range().length==2?e.range()[1]:e.range()[e.range().length-1]+(e.range()[1]-e.range()[0]);u.attr("x",v/2);if(g){var w=p.selectAll("g.nv-axisMaxMin").data([e.domain()[0],e.domain()[e.domain().length-1]]);w.enter().append("g").attr("class","nv-axisMaxMin").append("text"),w.exit().remove(),w.attr("transform",function(a,b){return"translate("+(e(a)+(l?e.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",a.tickPadding()).attr("transform",function(a,b,c){return"rotate("+i+" 0,0)"}).attr("text-anchor",i?i%360>0?"start":"end":"middle").text(function(a,b){var c=t(a);return(""+c).match("NaN")?"":c}),d3.transition(w).attr("transform",function(a,b){return"translate("+(e(a)+(l?e.rangeBand()/2:0))+",0)"})}k&&z.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":u.enter().append("text").attr("class","nv-axislabel").attr("text-anchor",j?"middle":"begin").attr("transform",j?"rotate(90)":"").attr("y",j?-Math.max(b.right,c)+12:-10),u.attr("x",j?e.range()[0]/2:a.tickPadding());if(g){var w=p.selectAll("g.nv-axisMaxMin").data(e.domain());w.enter().append("g").attr("class","nv-axisMaxMin").append("text").style("opacity",0),w.exit().remove(),w.attr("transform",function(a,b){return"translate(0,"+e(a)+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",a.tickPadding()).attr("text-anchor","start").text(function(a,b){var c=t(a);return(""+c).match("NaN")?"":c}),d3.transition(w).attr("transform",function(a,b){return"translate(0,"+e.range()[b]+")"}).select("text").style("opacity",1)}break;case"left":u.enter().append("text").attr("class","nv-axislabel").attr("text-anchor",j?"middle":"end").attr("transform",j?"rotate(-90)":"").attr("y",j?-Math.max(b.left,c)+12:-10),u.attr("x",j?-e.range()[0]/2:-a.tickPadding());if(g){var w=p.selectAll("g.nv-axisMaxMin").data(e.domain());w.enter().append("g").attr("class","nv-axisMaxMin").append("text").style("opacity",0),w.exit().remove(),w.attr("transform",function(a,b){return"translate(0,"+n(a)+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-a.tickPadding()).attr("text-anchor","end").text(function(a,b){var c=t(a);return(""+c).match("NaN")?"":c}),d3.transition(w).attr("transform",function(a,b){return"translate(0,"+e.range()[b]+")"}).select("text").style("opacity",1)}}u.text(function(a){return a}),g&&(a.orient()==="left"||a.orient()==="right")&&(s.selectAll("g").each(function(a,b){d3.select(this).select("text").attr("opacity",1);if(e(a)e.range()[0]-10)(a>1e-10||a<-1e-10)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0)}),e.domain()[0]==e.domain()[1]&&e.domain()[0]==0&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1}));if(g&&(a.orient()==="top"||a.orient()==="bottom")){var B=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{b?B.push(e(a)-this.getBBox().width-4):B.push(e(a)+this.getBBox().width+4)}catch(c){b?B.push(e(a)-4):B.push(e(a)+4)}}),s.selectAll("g").each(function(a,b){if(e(a)B[1])a>1e-10||a<-1e-10?d3.select(this).remove():d3.select(this).select("text").remove()})}h&&s.selectAll("line.tick").filter(function(a){return!parseFloat(Math.round(a*1e5)/1e6)}).classed("zero",!0),n=e.copy()}),o}var a=d3.svg.axis(),b={top:0,right:0,bottom:0,left:0},c=75,d=60,e=d3.scale.linear(),f=null,g=!0,h=!0,i=0,j=!0,k=!1,l=!1,m=null;a.scale(e).orient("bottom").tickFormat(function(a){return a});var n;return o.axis=a,d3.rebind(o,a,"orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"),d3.rebind(o,e,"domain","range","rangeBand","rangeBands"),o.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,o):b},o.width=function(a){return arguments.length?(c=a,o):c},o.ticks=function(a){return arguments.length?(m=a,o):m},o.height=function(a){return arguments.length?(d=a,o):d},o.axisLabel=function(a){return arguments.length?(f=a,o):f},o.showMaxMin=function(a){return arguments.length?(g=a,o):g},o.highlightZero=function(a){return arguments.length?(h=a,o):h},o.scale=function(b){return arguments.length?(e=b,a.scale(e),l=typeof e.rangeBands=="function",d3.rebind(o,e,"domain","range","rangeBand","rangeBands"),o):e},o.rotateYLabel=function(a){return arguments.length?(j=a,o):j},o.rotateLabels=function(a){return arguments.length?(i=a,o):i},o.staggerLabels=function(a){return arguments.length?(k=a,o):k},o},a.models.historicalBar=function(){function r(a){return a.each(function(a){var r=c-b.left-b.right,s=d-b.top-b.bottom,t=d3.select(this);f.domain(o||d3.extent(a[0].values.map(h).concat(j))),l?f.range([r*.5/a[0].values.length,r*(a[0].values.length-.5)/a[0].values.length]):f.range([0,r]),g.domain(p||d3.extent(a[0].values.map(i).concat(k))).range([s,0]);if(f.domain()[0]===f.domain()[1]||g.domain()[0]===g.domain()[1])singlePoint=!0;f.domain()[0]===f.domain()[1]&&(f.domain()[0]?f.domain([f.domain()[0]-f.domain()[0]*.01,f.domain()[1]+f.domain()[1]*.01]):f.domain([-1,1])),g.domain()[0]===g.domain()[1]&&(g.domain()[0]?g.domain([g.domain()[0]+g.domain()[0]*.01,g.domain()[1]-g.domain()[1]*.01]):g.domain([-1,1]));var u=t.selectAll("g.nv-wrap.nv-bar").data([a[0].values]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-bar"),w=v.append("defs"),z=v.append("g"),A=u.select("g");z.append("g").attr("class","nv-bars"),u.attr("transform","translate("+b.left+","+b.top+")"),t.on("click",function(a,b){q.chartClick({data:a,index:b,pos:d3.event,id:e})}),w.append("clipPath").attr("id","nv-chart-clip-path-"+e).append("rect"),u.select("#nv-chart-clip-path-"+e+" rect").attr("width",r).attr("height",s),A.attr("clip-path",m?"url(#nv-chart-clip-path-"+e+")":"");var B=u.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a});B.exit().remove();var C=B.enter().append("rect").attr("x",0).attr("y",function(a,b){return g(Math.max(0,i(a,b)))}).attr("height",function(a,b){return Math.abs(g(i(a,b))-g(0))}).on("mouseover",function(b,c){d3.select(this).classed("hover",!0),q.elementMouseover({point:b,series:a[0],pos:[f(h(b,c)),g(i(b,c))],pointIndex:c,seriesIndex:0,e:d3.event})}).on("mouseout",function(b,c){d3.select(this).classed("hover",!1),q.elementMouseout({point:b,series:a[0],pointIndex:c,seriesIndex:0,e:d3.event})}).on("click",function(a,b){q.elementClick({value:i(a,b),data:a,index:b,pos:[f(h(a,b)),g(i(a,b))],e:d3.event,id:e}),d3.event.stopPropagation()}).on("dblclick",function(a,b){q.elementDblClick({value:i(a,b),data:a,index:b,pos:[f(h(a,b)),g(i(a,b))],e:d3.event,id:e}),d3.event.stopPropagation()});B.attr("fill",function(a,b){return n(a,b)}).attr("class",function(a,b,c){return(i(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).attr("transform",function(b,c){return"translate("+(f(h(b,c))-r/a[0].values.length*.45)+",0)"}).attr("width",r/a[0].values.length*.9),d3.transition(B).attr("y",function(a,b){return i(a,b)<0?g(0):g(0)-g(i(a,b))<1?g(0)-1:g(i(a,b))}).attr("height",function(a,b){return Math.max(Math.abs(g(i(a,b))-g(0)),1)})}),r}var b={top:0,right:0,bottom:0,left:0},c=960,d=500,e=Math.floor(Math.random()*1e4),f=d3.scale.linear(),g=d3.scale.linear(),h=function(a){return a.x},i=function(a){return a.y},j=[],k=[0],l=!1,m=!0,n=a.utils.defaultColor(),o,p,q=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return r.dispatch=q,r.x=function(a){return arguments.length?(h=a,r):h},r.y=function(a){return arguments.length?(i=a,r):i},r.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,r):b},r.width=function(a){return arguments.length?(c=a,r):c},r.height=function(a){return arguments.length?(d=a,r):d},r.xScale=function(a){return arguments.length?(f=a,r):f},r.yScale=function(a){return arguments.length?(g=a,r):g},r.xDomain=function(a){return arguments.length?(o=a,r):o},r.yDomain=function(a){return arguments.length?(p=a,r):p},r.forceX=function(a){return arguments.length?(j=a,r):j},r.forceY=function(a){return arguments.length?(k=a,r):k},r.padData=function(a){return arguments.length?(l=a,r):l},r.clipEdge=function(a){return arguments.length?(m=a,r):m},r.color=function(b){return arguments.length?(n=a.utils.getColor(b),r):n},r.id=function(a){return arguments.length?(e=a,r):e},r},a.models.bullet=function(){function n(a){return a.each(function(a,c){var k=i-b.left-b.right,n=j-b.top-b.bottom,o=d3.select(this),p=e.call(this,a,c).slice().sort(d3.descending),q=f.call(this,a,c).slice().sort(d3.descending),r=g.call(this,a,c).slice().sort(d3.descending),s=d3.scale.linear().domain(d3.extent(d3.merge([h,p]))).range(d?[k,0]:[0,k]),t=this.__chart__||d3.scale.linear().domain([0,Infinity]).range(s.range());this.__chart__=s;var u=d3.min(p),v=d3.max(p),w=p[1],x=o.selectAll("g.nv-wrap.nv-bullet").data([a]),y=x.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),z=y.append("g"),A=x.select("g");z.append("rect").attr("class","nv-range nv-rangeMax"),z.append("rect").attr("class","nv-range nv-rangeAvg"),z.append("rect").attr("class","nv-range nv-rangeMin"),z.append("rect").attr("class","nv-measure"),z.append("path").attr("class","nv-markerTriangle"),x.attr("transform","translate("+b.left+","+b.top+")");var B=function(a){return Math.abs(t(a)-t(0))},C=function(a){return Math.abs(s(a)-s(0))},D=function(a){return a<0?t(a):t(0)},E=function(a){return a<0?s(a):s(0)};A.select("rect.nv-rangeMax").attr("height",n).attr("width",C(v>0?v:u)).attr("x",E(v>0?v:u)).datum(v>0?v:u),A.select("rect.nv-rangeAvg").attr("height",n).attr("width",C(w)).attr("x",E(w)).datum(w),A.select("rect.nv-rangeMin").attr("height",n).attr("width",C(v)).attr("x",E(v)).attr("width",C(v>0?u:v)).attr("x",E(v>0?u:v)).datum(v>0?u:v),A.select("rect.nv-measure").style("fill",l).attr("height",n/3).attr("y",n/3).attr("width",r<0?s(0)-s(r[0]):s(r[0])-s(0)).attr("x",E(r)).on("mouseover",function(){m.elementMouseover({value:r[0],label:"Current",pos:[s(r[0]),n/2]})}).on("mouseout",function(){m.elementMouseout({value:r[0],label:"Current"})});var F=n/6;q[0]?A.selectAll("path.nv-markerTriangle").attr("transform",function(a){return"translate("+s(q[0])+","+n/2+")"}).attr("d","M0,"+F+"L"+F+","+ -F+" "+ -F+","+ -F+"Z").on("mouseover",function(){m.elementMouseover({value:q[0],label:"Previous",pos:[s(q[0]),n/2]})}).on("mouseout",function(){m.elementMouseout({value:q[0],label:"Previous"})}):A.selectAll("path.nv-markerTriangle").remove(),x.selectAll(".nv-range").on("mouseover",function(a,b){var c=b?b==1?"Mean":"Minimum":"Maximum";m.elementMouseover({value:a,label:c,pos:[s(a),n/2]})}).on("mouseout",function(a,b){var c=b?b==1?"Mean":"Minimum":"Maximum";m.elementMouseout({value:a,label:c})})}),n}var b={top:0,right:0,bottom:0,left:0},c="left",d=!1,e=function(a){return a.ranges},f=function(a){return a.markers},g=function(a){return a.measures},h=[0],i=380,j=30,k=null,l=a.utils.getColor(["#1f77b4"]),m=d3.dispatch("elementMouseover","elementMouseout");return n.dispatch=m,n.orient=function(a){return arguments.length?(c=a,d=c=="right"||c=="bottom",n):c},n.ranges=function(a){return arguments.length?(e=a,n):e},n.markers=function(a){return arguments.length?(f=a,n):f},n.measures=function(a){return arguments.length?(g=a,n):g},n.forceX=function(a){return arguments.length?(h=a,n):h},n.width=function(a){return arguments.length?(i=a,n):i},n.height=function(a){return arguments.length?(j=a,n):j},n.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,n):b},n.tickFormat=function(a){return arguments.length?(k=a,n):k},n.color=function(b){return arguments.length?(l=a.utils.getColor(b),n):l},n},a.models.bulletChart=function(){function q(a){return a.each(function(c,m){var r=d3.select(this),s=(i||parseInt(r.style("width"))||960)-e.left-e.right,t=j-e.top-e.bottom,u=this;q.update=function(){q(a)},q.container=this;if(!c||!f.call(this,c,m)){var v=r.selectAll(".nv-noData").data([n]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",e.left+s/2).attr("y",18+e.top+t/2).text(function(a){return a}),q}r.selectAll(".nv-noData").remove();var w=f.call(this,c,m).slice().sort(d3.descending),x=g.call(this,c,m).slice().sort(d3.descending),y=h.call(this,c,m).slice().sort(d3.descending),z=r.selectAll("g.nv-wrap.nv-bulletChart").data([c]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-bulletWrap"),B.append("g").attr("class","nv-titles"),z.attr("transform","translate("+e.left+","+e.top+")");var D=d3.scale.linear().domain([0,Math.max(w[0],x[0],y[0])]).range(d?[s,0]:[0,s]),E=this.__chart__||d3.scale.linear().domain([0,Infinity]).range(D.range());this.__chart__=D;var F=function(a){return Math.abs(E(a)-E(0))},G=function(a){return Math.abs(D(a)-D(0))},H=B.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(j-e.top-e.bottom)/2+")");H.append("text").attr("class","nv-title").text(function(a){return a.title}),H.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),b.width(s).height(t);var I=C.select(".nv-bulletWrap");d3.transition(I).call(b);var J=k||D.tickFormat(s/100),K=C.selectAll("g.nv-tick").data(D.ticks(s/50),function(a){return this.textContent||J(a)}),L=K.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+E(a)+",0)"}).style("opacity",1e-6);L.append("line").attr("y1",t).attr("y2",t*7/6),L.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",t*7/6).text(J);var M=d3.transition(K).attr("transform",function(a){return"translate("+D(a)+",0)"}).style("opacity",1);M.select("line").attr("y1",t).attr("y2",t*7/6),M.select("text").attr("y",t*7/6),d3.transition(K.exit()).attr("transform",function(a){return"translate("+D(a)+",0)"}).style("opacity",1e-6).remove(),o.on("tooltipShow",function(a){a.key=data[0].title,l&&p(a,u.parentNode)})}),d3.timer.flush(),q}var b=a.models.bullet(),c="left",d=!1,e={top:5,right:40,bottom:20,left:120},f=function(a){return a.ranges},g=function(a){return a.markers},h=function(a){return a.measures},i=null,j=55,k=null,l=!0,m=function(a,b,c,d,e){return"

"+b+"

"+"

"+c+"

"},n="No Data Available.",o=d3.dispatch("tooltipShow","tooltipHide"),p=function(b,c){var d=b.pos[0]+(c.offsetLeft||0)+e.left,f=b.pos[1]+(c.offsetTop||0)+e.top,g=m(b.key,b.label,b.value,b,q);a.tooltip.show([d,f],g,b.value<0?"e":"w",null,c)};return b.dispatch.on("elementMouseover.tooltip",function(a){o.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){o.tooltipHide(a)}),o.on("tooltipHide",function(){l&&a.tooltip.cleanup()}),q.dispatch=o,q.bullet=b,d3.rebind(q,b,"color"),q.orient=function(a){return arguments.length?(c=a,d=c=="right"||c=="bottom",q):c},q.ranges=function(a){return arguments.length?(f=a,q):f},q.markers=function(a){return arguments.length?(g=a,q):g},q.measures=function(a){return arguments.length?(h=a,q):h},q.width=function(a){return arguments.length?(i=a,q):i},q.height=function(a){return arguments.length?(j=a,q):j},q.margin=function(a){return arguments.length?(e.top=typeof a.top!="undefined"?a.top:e.top,e.right=typeof a.right!="undefined"?a.right:e.right,e.bottom=typeof a.bottom!="undefined"?a.bottom:e.bottom,e.left=typeof a.left!="undefined"?a.left:e.left,q):e},q.tickFormat=function(a){return arguments.length?(k=a,q):k},q.tooltips=function(a){return arguments.length?(l=a,q):l},q.tooltipContent=function(a){return arguments.length?(m=a,q):m},q.noData=function(a){return arguments.length?(n=a,q):n},q},a.models.cumulativeLineChart=function(){function y(a){return a.each(function(o){function F(a,b){d3.select(y.container).style("cursor","ew-resize")}function G(a,b){w.x=d3.event.x,w.i=Math.round(v.invert(w.x)),S()}function H(a,b){d3.select(y.container).style("cursor","auto"),s.index=w.i,u.stateChange(s)}function S(){R.data([w]),y.update()}var A=d3.select(this).classed("nv-chart-"+r,!0),B=this,C=(i||parseInt(A.style("width"))||960)-g.left-g.right,D=(j||parseInt(A.style("height"))||400)-g.top-g.bottom;y.update=function(){y(a)},y.container=this;var E=d3.behavior.drag().on("dragstart",F).on("drag",G).on("dragend",H);if(!o||!o.length||!o.filter(function(a){return a.values.length}).length){var I=A.selectAll(".nv-noData").data([t]);return I.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),I.attr("x",g.left+C/2).attr("y",g.top+D/2).text(function(a){return a}),y}A.selectAll(".nv-noData").remove(),p=b.xScale(),q=b.yScale();if(!n){var J=o.filter(function(a){return!a.disabled}).map(function(a,c){var d=d3.extent(a.values,b.y());return d[0]<-0.95&&(d[0]=-0.95),[(d[0]-d[1])/(1+d[1]),(d[1]-d[0])/(1+d[0])]}),K=[d3.min(J,function(a){return a[0]}),d3.max(J,function(a){return a[1]})];b.yDomain(K)}else b.yDomain(null);v.domain([0,o[0].values.length-1]).range([0,C]).clamp(!0);var o=z(w.i,o),L=A.selectAll("g.nv-wrap.nv-cumulativeLine").data([o]),M=L.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),N=L.select("g");M.append("g").attr("class","nv-x nv-axis"),M.append("g").attr("class","nv-y nv-axis"),M.append("g").attr("class","nv-background"),M.append("g").attr("class","nv-linesWrap"),M.append("g").attr("class","nv-legendWrap"),M.append("g").attr("class","nv-controlsWrap"),k&&(e.width(C),N.select(".nv-legendWrap").datum(o).call(e),g.top!=e.height()&&(g.top=e.height(),D=(j||parseInt(A.style("height"))||400)-g.top-g.bottom),N.select(".nv-legendWrap").attr("transform","translate(0,"+ -g.top+")"));if(m){var O=[{key:"Re-scale y-axis",disabled:!n}];f.width(140).color(["#444","#444","#444"]),N.select(".nv-controlsWrap").datum(O).attr("transform","translate(0,"+ -g.top+")").call(f)}L.attr("transform","translate("+g.left+","+g.top+")");var P=o.filter(function(a){return a.tempDisabled});L.select(".tempDisabled").remove(),P.length&&L.append("text").attr("class","tempDisabled").attr("x",C/2).attr("y","-.71em").style("text-anchor","end").text(P.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),M.select(".nv-background").append("rect"),N.select(".nv-background rect").attr("width",C).attr("height",D),b.y(function(a){return a.display.y}).width(C).height(D).color(o.map(function(a,b){return a.color||h(a,b)}).filter(function(a,b){return!o[b].disabled&&!o[b].tempDisabled}));var Q=N.select(".nv-linesWrap").datum(o.filter(function(a){return!a.disabled&&!a.tempDisabled}));Q.call(b);var R=Q.selectAll(".nv-indexLine").data([w]);R.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).call(E),R.attr("transform",function(a){return"translate("+v(a.i)+",0)"}).attr("height",D),c.scale(p).ticks(Math.min(o[0].values.length,C/70)).tickSize(-D,0),N.select(".nv-x.nv-axis").attr("transform","translate(0,"+q.range()[0]+")"),d3.transition(N.select(".nv-x.nv-axis")).call(c),d.scale(q).ticks(D/36).tickSize(-C,0),d3.transition(N.select(".nv-y.nv-axis")).call(d),N.select(".nv-background rect").on("click",function(){w.x=d3.mouse(this)[0],w.i=Math.round(v.invert(w.x)),s.index=w.i,u.stateChange(s),S()}),b.dispatch.on("elementClick",function(a){w.i=a.pointIndex,w.x=v(w.i),s.index=w.i,u.stateChange(s),S()}),f.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,n=!b.disabled,s.rescaleY=n,u.stateChange(s),a.call(y)}),e.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,o.filter(function(a){return!a.disabled}).length||o.map(function(a){return a.disabled=!1,L.selectAll(".nv-series").classed("disabled",!1),a}),s.disabled=o.map(function(a){return!!a.disabled}),u.stateChange(s),a.call(y)}),u.on("tooltipShow",function(a){l&&x(a,B.parentNode)}),u.on("changeState",function(b){typeof b.disabled!="undefined"&&(o.forEach(function(a,c){a.disabled=b.disabled[c]}),s.disabled=b.disabled),typeof b.index!="undefined"&&(w.i=b.index,w.x=v(w.i),s.index=b.index,R.data([w])),typeof b.rescaleY!="undefined"&&(n=b.rescaleY),a.call(y)})}),y}function z(a,c){return c.map(function(c,d){var e=b.y()(c.values[a],a);return e<-0.95?(c.tempDisabled=!0,c):(c.tempDisabled=!1,c.values=c.values.map(function(a,c){return a.display={y:(b.y()(a,c)-e)/(1+e)},a}),c)})}var b=a.models.line(),c=a.models.axis(),d=a.models.axis(),e=a.models.legend(),f=a.models.legend(),g={top:30,right:30,bottom:50,left:60},h=a.utils.defaultColor(),i=null,j=null,k=!0,l=!0,m=!0,n=!0,o=function(a,b,c,d,e){return"

"+a+"

"+"

"+c+" at "+b+"

"},p,q,r=b.id(),s={index:0,rescaleY:n},t="No Data Available.",u=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");c.orient("bottom").tickPadding(7),d.orient("left");var v=d3.scale.linear(),w={i:0,x:0},x=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),i=c.tickFormat()(b.x()(e.point,e.pointIndex)),j=d.tickFormat()(b.y()(e.point,e.pointIndex)),k=o(e.series.key,i,j,e,y);a.tooltip.show([g,h],k,null,null,f)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+g.left,a.pos[1]+g.top],u.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){u.tooltipHide(a)}),u.on("tooltipHide",function(){l&&a.tooltip.cleanup()}),y.dispatch=u,y.lines=b,y.legend=e,y.xAxis=c,y.yAxis=d,d3.rebind(y,b,"defined","isArea","x","y","size","xDomain","yDomain","forceX","forceY","interactive","clipEdge","clipVoronoi","id"),y.margin=function(a){return arguments.length?(g.top=typeof a.top!="undefined"?a.top:g.top,g.right=typeof a.right!="undefined"?a.right:g.right,g.bottom=typeof a.bottom!="undefined"?a.bottom:g.bottom,g.left=typeof a.left!="undefined"?a.left:g.left,y):g},y.width=function(a){return arguments.length?(i=a,y):i},y.height=function(a){return arguments.length?(j=a,y):j},y.color=function(b){return arguments.length?(h=a.utils.getColor(b),e.color(h),y):h},y.rescaleY=function(a){return arguments.length?(n=a,n):n},y.showControls=function(a){return arguments.length?(m=a,y):m},y.showLegend=function(a){return arguments.length?(k=a,y):k},y.tooltips=function(a){return arguments.length?(l=a,y):l},y.tooltipContent=function(a){return arguments.length?(o=a,y):o},y.state=function(a){return arguments.length?(s=a,y):s},y.noData=function(a){return arguments.length?(t=a,y):t},y},a.models.discreteBar=function(){function t(a){return a.each(function(a){var e=c-b.left-b.right,t=d-b.top-b.bottom,u=d3.select(this);a=a.map(function(a,b){return a.values=a.values.map(function(a){return a.series=b,a}),a});var v=n&&o?[]:a.map(function(a){return a.values.map(function(a,b){return{x:h(a,b),y:i(a,b),y0:a.y0}})});f.domain(n||d3.merge(v).map(function(a){return a.x})).rangeBands([0,e],.1),g.domain(o||d3.extent(d3.merge(v).map(function(a){return a.y}).concat(j))),l?g.range([t-(g.domain()[0]<0?12:0),g.domain()[1]>0?12:0]):g.range([t,0]),r=r||f,s=s||g.copy().range([g(0),g(0)]);var w=u.selectAll("g.nv-wrap.nv-discretebar").data([a]),z=w.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),A=z.append("g"),B=w.select("g");A.append("g").attr("class","nv-groups"),w.attr("transform","translate("+b.left+","+b.top+")");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(C.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),C.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),d3.transition(C).style("stroke-opacity",1).style("fill-opacity",.75);var D=C.selectAll("g.nv-bar").data(function(a){return a.values});D.exit().remove();var E=D.enter().append("g").attr("transform",function(a,b,c){return"translate("+(f(h(a,b))+f.rangeBand()*.05)+", "+g(0)+")"}).on("mouseover",function(b,c){d3.select(this).classed("hover",!0),p.elementMouseover({value:i(b,c),point:b,series:a[b.series],pos:[f(h(b,c))+f.rangeBand()*(b.series+.5)/a.length,g(i(b,c))],pointIndex:c,seriesIndex:b.series,e:d3.event})}).on("mouseout",function(b,c){d3.select(this).classed("hover",!1),p.elementMouseout({value:i(b,c),point:b,series:a[b.series],pointIndex:c,seriesIndex:b.series,e:d3.event})}).on("click",function(b,c){p.elementClick({value:i(b,c),point:b,series:a[b.series],pos:[f(h(b,c))+f.rangeBand()*(b.series+.5)/a.length,g(i(b,c))],pointIndex:c,seriesIndex:b.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(b,c){p.elementDblClick({value:i(b,c),point:b,series:a[b.series],pos:[f(h(b,c))+f.rangeBand()*(b.series+.5)/a.length,g(i(b,c))],pointIndex:c,seriesIndex:b.series,e:d3.event}),d3.event.stopPropagation()});E.append("rect").attr("height",0).attr("width",f.rangeBand()*.9/a.length),l?(E.append("text").attr("text-anchor","middle"),D.select("text").attr("x",f.rangeBand()*.9/2).attr("y",function(a,b){return i(a,b)<0?g(i(a,b))-g(0)+12:-4}).text(function(a,b){return m(i(a,b))})):D.selectAll("text").remove(),D.attr("class",function(a,b){return i(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||k(a,b)}).style("stroke",function(a,b){return a.color||k(a,b)}).select("rect").attr("class",q).attr("width",f.rangeBand()*.9/a.length),d3.transition(D).attr("transform",function(a,b){var c=f(h(a,b))+f.rangeBand()*.05,d=i(a,b)<0?g(0):g(0)-g(i(a,b))<1?g(0)-1:g(i(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(g(i(a,b))-g(0))||1)}),r=f.copy(),s=g.copy()}),t}var b={top:0,right:0,bottom:0,left:0},c=960,d=500,e=Math.floor(Math.random()*1e4),f=d3.scale.ordinal(),g=d3.scale.linear(),h=function(a){return a.x},i=function(a){return a.y},j=[0],k=a.utils.defaultColor(),l=!1,m=d3.format(",.2f"),n,o,p=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),q="discreteBar",r,s;return t.dispatch=p,t.x=function(a){return arguments.length?(h=a,t):h},t.y=function(a){return arguments.length?(i=a,t):i},t.margin=function(a){return arguments.length? +(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,t):b},t.width=function(a){return arguments.length?(c=a,t):c},t.height=function(a){return arguments.length?(d=a,t):d},t.xScale=function(a){return arguments.length?(f=a,t):f},t.yScale=function(a){return arguments.length?(g=a,t):g},t.xDomain=function(a){return arguments.length?(n=a,t):n},t.yDomain=function(a){return arguments.length?(o=a,t):o},t.forceY=function(a){return arguments.length?(j=a,t):j},t.color=function(b){return arguments.length?(k=a.utils.getColor(b),t):k},t.id=function(a){return arguments.length?(e=a,t):e},t.showValues=function(a){return arguments.length?(l=a,t):l},t.valueFormat=function(a){return arguments.length?(m=a,t):m},t.rectClass=function(a){return arguments.length?(q=a,t):q},t},a.models.discreteBarChart=function(){function q(a){return a.each(function(h){var k=d3.select(this),r=this,s=(f||parseInt(k.style("width"))||960)-e.left-e.right,t=(g||parseInt(k.style("height"))||400)-e.top-e.bottom;q.update=function(){o.beforeUpdate(),a.transition().call(q)},q.container=this;if(!h||!h.length||!h.filter(function(a){return a.values.length}).length){var u=k.selectAll(".nv-noData").data([n]);return u.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),u.attr("x",e.left+s/2).attr("y",e.top+t/2).text(function(a){return a}),q}k.selectAll(".nv-noData").remove(),l=b.xScale(),m=b.yScale();var v=k.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),z=w.append("defs"),A=v.select("g");w.append("g").attr("class","nv-x nv-axis"),w.append("g").attr("class","nv-y nv-axis"),w.append("g").attr("class","nv-barsWrap"),A.attr("transform","translate("+e.left+","+e.top+")"),b.width(s).height(t);var B=A.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));d3.transition(B).call(b),z.append("clipPath").attr("id","nv-x-label-clip-"+b.id()).append("rect"),A.select("#nv-x-label-clip-"+b.id()+" rect").attr("width",l.rangeBand()*(i?2:1)).attr("height",16).attr("x",-l.rangeBand()/(i?1:2)),c.scale(l).ticks(s/100).tickSize(-t,0),A.select(".nv-x.nv-axis").attr("transform","translate(0,"+(m.range()[0]+(b.showValues()&&m.domain()[0]<0?16:0))+")"),A.select(".nv-x.nv-axis").transition().duration(0).call(c);var C=A.select(".nv-x.nv-axis").selectAll("g");i&&C.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"}),d.scale(m).ticks(t/36).tickSize(-s,0),d3.transition(A.select(".nv-y.nv-axis")).call(d),o.on("tooltipShow",function(a){j&&p(a,r.parentNode)})}),q}var b=a.models.discreteBar(),c=a.models.axis(),d=a.models.axis(),e={top:15,right:10,bottom:50,left:60},f=null,g=null,h=a.utils.getColor(),i=!1,j=!0,k=function(a,b,c,d,e){return"

"+b+"

"+"

"+c+"

"},l,m,n="No Data Available.",o=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate");c.orient("bottom").highlightZero(!1).showMaxMin(!1).tickFormat(function(a){return a}),d.orient("left").tickFormat(d3.format(",.1f"));var p=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),i=c.tickFormat()(b.x()(e.point,e.pointIndex)),j=d.tickFormat()(b.y()(e.point,e.pointIndex)),l=k(e.series.key,i,j,e,q);a.tooltip.show([g,h],l,e.value<0?"n":"s",null,f)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+e.left,a.pos[1]+e.top],o.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){o.tooltipHide(a)}),o.on("tooltipHide",function(){j&&a.tooltip.cleanup()}),q.dispatch=o,q.discretebar=b,q.xAxis=c,q.yAxis=d,d3.rebind(q,b,"x","y","xDomain","yDomain","forceX","forceY","id","showValues","valueFormat"),q.margin=function(a){return arguments.length?(e.top=typeof a.top!="undefined"?a.top:e.top,e.right=typeof a.right!="undefined"?a.right:e.right,e.bottom=typeof a.bottom!="undefined"?a.bottom:e.bottom,e.left=typeof a.left!="undefined"?a.left:e.left,q):e},q.width=function(a){return arguments.length?(f=a,q):f},q.height=function(a){return arguments.length?(g=a,q):g},q.color=function(c){return arguments.length?(h=a.utils.getColor(c),b.color(h),q):h},q.staggerLabels=function(a){return arguments.length?(i=a,q):i},q.tooltips=function(a){return arguments.length?(j=a,q):j},q.tooltipContent=function(a){return arguments.length?(k=a,q):k},q.noData=function(a){return arguments.length?(n=a,q):n},q},a.models.distribution=function(){function k(a){return a.each(function(a){var i=c-(e==="x"?b.left+b.right:b.top+b.bottom),k=e=="x"?"y":"x",l=d3.select(this);j=j||h;var m=l.selectAll("g.nv-distribution").data([a]),n=m.enter().append("g").attr("class","nvd3 nv-distribution"),o=n.append("g"),p=m.select("g");m.attr("transform","translate("+b.left+","+b.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return g(a,b)});var r=q.selectAll("line.nv-dist"+e).data(function(a){return a.values});r.enter().append("line").attr(e+"1",function(a,b){return j(f(a,b))}).attr(e+"2",function(a,b){return j(f(a,b))}),d3.transition(q.exit().selectAll("line.nv-dist"+e)).attr(e+"1",function(a,b){return h(f(a,b))}).attr(e+"2",function(a,b){return h(f(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+e+" nv-dist"+e+"-"+b}).attr(k+"1",0).attr(k+"2",d),d3.transition(r).attr(e+"1",function(a,b){return h(f(a,b))}).attr(e+"2",function(a,b){return h(f(a,b))}),j=h.copy()}),k}var b={top:0,right:0,bottom:0,left:0},c=400,d=8,e="x",f=function(a){return a[e]},g=a.utils.defaultColor(),h=d3.scale.linear(),i,j;return k.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,k):b},k.width=function(a){return arguments.length?(c=a,k):c},k.axis=function(a){return arguments.length?(e=a,k):e},k.size=function(a){return arguments.length?(d=a,k):d},k.getData=function(a){return arguments.length?(f=d3.functor(a),k):f},k.scale=function(a){return arguments.length?(h=a,k):h},k.color=function(b){return arguments.length?(g=a.utils.getColor(b),k):g},k},a.models.indentedTree=function(){function o(a){return a.each(function(b){function z(a,b,c){d3.event.stopPropagation();if(d3.event.shiftKey&&!c)return d3.event.shiftKey=!1,a.values&&a.values.forEach(function(a){(a.values||a._values)&&z(a,0,!0)}),!0;if(!C(a))return!0;a.values?(a._values=a.values,a.values=null):(a.values=a._values,a._values=null),o.update()}function A(a){return a._values&&a._values.length?l:a.values&&a.values.length?m:""}function B(a){return a._values&&a._values.length}function C(a){var b=a.values||a._values;return b&&b.length}var c=0,e=1,f=d3.layout.tree().children(function(a){return a.values}).size([d,i]);o.update=function(){a.transition().call(o)},o.container=this,b[0]||(b[0]={key:h});var p=f.nodes(b[0]),q=d3.select(this).selectAll("div").data([[p]]),r=q.enter().append("div").attr("class","nvd3 nv-wrap nv-indentedtree"),s=r.append("table"),t=q.select("table").attr("width","100%").attr("class",k);if(g){var u=s.append("thead"),v=u.append("tr");j.forEach(function(a){v.append("th").attr("width",a.width?a.width:"10%").style("text-align",a.type=="numeric"?"right":"left").append("span").text(a.label)})}var w=t.selectAll("tbody").data(function(a){return a});w.enter().append("tbody"),e=d3.max(p,function(a){return a.depth}),f.size([d,e*i]);var x=w.selectAll("tr").data(function(a){return a},function(a){return a.id||a.id==++c});x.exit().remove(),x.select("img.nv-treeicon").attr("src",A).classed("folded",B);var y=x.enter().append("tr");j.forEach(function(a,b){var c=y.append("td").style("padding-left",function(a){return(b?0:a.depth*i+12+(A(a)?0:16))+"px"},"important").style("text-align",a.type=="numeric"?"right":"left");b==0&&c.append("img").classed("nv-treeicon",!0).classed("nv-folded",B).attr("src",A).style("width","14px").style("height","14px").style("padding","0 1px").style("display",function(a){return A(a)?"inline-block":"none"}).on("click",z),c.append("span").attr("class",d3.functor(a.classes)).text(function(b){return a.format?a.format(b):b[a.key]||"-"}),a.showCount&&c.append("span").attr("class","nv-childrenCount").text(function(a){return a.values&&a.values.length||a._values&&a._values.length?"("+(a.values&&a.values.length||a._values&&a._values.length)+")":""}),a.click&&c.select("span").on("click",a.click)}),x.order().on("click",function(a){n.elementClick({row:this,data:a,pos:[a.x,a.y]})}).on("dblclick",function(a){n.elementDblclick({row:this,data:a,pos:[a.x,a.y]})}).on("mouseover",function(a){n.elementMouseover({row:this,data:a,pos:[a.x,a.y]})}).on("mouseout",function(a){n.elementMouseout({row:this,data:a,pos:[a.x,a.y]})})}),o}var b={top:0,right:0,bottom:0,left:0},c=960,d=500,e=a.utils.defaultColor(),f=Math.floor(Math.random()*1e4),g=!0,h="No Data Available.",i=20,j=[{key:"key",label:"Name",type:"text"}],k=null,l="images/grey-plus.png",m="images/grey-minus.png",n=d3.dispatch("elementClick","elementDblclick","elementMouseover","elementMouseout");return o.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,o):b},o.width=function(a){return arguments.length?(c=a,o):c},o.height=function(a){return arguments.length?(d=a,o):d},o.color=function(b){return arguments.length?(e=a.utils.getColor(b),scatter.color(e),o):e},o.id=function(a){return arguments.length?(f=a,o):f},o.header=function(a){return arguments.length?(g=a,o):g},o.noData=function(a){return arguments.length?(h=a,o):h},o.columns=function(a){return arguments.length?(j=a,o):j},o.tableClass=function(a){return arguments.length?(k=a,o):k},o.iconOpen=function(a){return arguments.length?(l=a,o):l},o.iconClose=function(a){return arguments.length?(m=a,o):m},o},a.models.legend=function(){function i(a){return a.each(function(a){var i=c-b.left-b.right,j=d3.select(this),l=j.selectAll("g.nv-legend").data([a]),m=l.enter().append("g").attr("class","nvd3 nv-legend").append("g"),n=l.select("g");l.attr("transform","translate("+b.left+","+b.top+")");var o=n.selectAll(".nv-series").data(function(a){return a}),p=o.enter().append("g").attr("class","nv-series").on("mouseover",function(a,b){h.legendMouseover(a,b)}).on("mouseout",function(a,b){h.legendMouseout(a,b)}).on("click",function(a,b){h.legendClick(a,b)}).on("dblclick",function(a,b){h.legendDblclick(a,b)});p.append("circle").style("stroke-width",2).attr("r",5),p.append("text").attr("text-anchor","start").attr("dy",".32em").attr("dx","8"),o.classed("disabled",function(a){return a.disabled}),o.exit().remove(),o.select("circle").style("fill",function(a,b){return a.color||f(a,b)}).style("stroke",function(a,b){return a.color||f(a,b)}),o.select("text").text(e);if(g){var q=[];o.each(function(a,b){q.push(d3.select(this).select("text").node().getComputedTextLength()+28)});var r=0,s=0,t=[];while(si&&r>1){t=[],r--;for(k=0;k(t[k%r]||0)&&(t[k%r]=q[k]);s=t.reduce(function(a,b,c,d){return a+b})}var u=[];for(var v=0,w=0;vz&&(z=y),"translate("+A+","+x+")"}),n.attr("transform","translate("+(c-b.right-z)+","+b.top+")"),d=b.top+b.bottom+x+15}}),i}var b={top:5,right:0,bottom:5,left:0},c=400,d=20,e=function(a){return a.key},f=a.utils.defaultColor(),g=!0,h=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout");return i.dispatch=h,i.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,i):b},i.width=function(a){return arguments.length?(c=a,i):c},i.height=function(a){return arguments.length?(d=a,i):d},i.key=function(a){return arguments.length?(e=a,i):e},i.color=function(b){return arguments.length?(f=a.utils.getColor(b),i):f},i.align=function(a){return arguments.length?(g=a,i):g},i},a.models.line=function(){function q(a){return a.each(function(a){var q=d-c.left-c.right,r=e-c.top-c.bottom,s=d3.select(this);l=b.xScale(),m=b.yScale(),o=o||l,p=p||m;var t=s.selectAll("g.nv-wrap.nv-line").data([a]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),v=u.append("defs"),w=u.append("g"),z=t.select("g");w.append("g").attr("class","nv-groups"),w.append("g").attr("class","nv-scatterWrap"),t.attr("transform","translate("+c.left+","+c.top+")"),b.width(q).height(r);var A=t.select(".nv-scatterWrap");d3.transition(A).call(b),v.append("clipPath").attr("id","nv-edge-clip-"+b.id()).append("rect"),t.select("#nv-edge-clip-"+b.id()+" rect").attr("width",q).attr("height",r),z.attr("clip-path",k?"url(#nv-edge-clip-"+b.id()+")":""),A.attr("clip-path",k?"url(#nv-edge-clip-"+b.id()+")":"");var B=t.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});B.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(B.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),B.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return f(a,b)}).style("stroke",function(a,b){return f(a,b)}),d3.transition(B).style("stroke-opacity",1).style("fill-opacity",.5);var C=B.selectAll("path.nv-area").data(function(a){return j(a)?[a]:[]});C.enter().append("path").attr("class","nv-area").attr("d",function(a){return d3.svg.area().interpolate(n).defined(i).x(function(a,b){return o(g(a,b))}).y0(function(a,b){return p(h(a,b))}).y1(function(a,b){return p(m.domain()[0]<=0?m.domain()[1]>=0?0:m.domain()[1]:m.domain()[0])}).apply(this,[a.values])}),d3.transition(B.exit().selectAll("path.nv-area")).attr("d",function(a){return d3.svg.area().interpolate(n).defined(i).x(function(a,b){return o(g(a,b))}).y0(function(a,b){return p(h(a,b))}).y1(function(a,b){return p(m.domain()[0]<=0?m.domain()[1]>=0?0:m.domain()[1]:m.domain()[0])}).apply(this,[a.values])}),d3.transition(C).attr("d",function(a){return d3.svg.area().interpolate(n).defined(i).x(function(a,b){return o(g(a,b))}).y0(function(a,b){return p(h(a,b))}).y1(function(a,b){return p(m.domain()[0]<=0?m.domain()[1]>=0?0:m.domain()[1]:m.domain()[0])}).apply(this,[a.values])});var D=B.selectAll("path.nv-line").data(function(a){return[a.values]});D.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(n).defined(i).x(function(a,b){return o(g(a,b))}).y(function(a,b){return p(h(a,b))})),d3.transition(B.exit().selectAll("path.nv-line")).attr("d",d3.svg.line().interpolate(n).defined(i).x(function(a,b){return l(g(a,b))}).y(function(a,b){return m(h(a,b))})),d3.transition(D).attr("d",d3.svg.line().interpolate(n).defined(i).x(function(a,b){return l(g(a,b))}).y(function(a,b){return m(h(a,b))})),o=l.copy(),p=m.copy()}),q}var b=a.models.scatter(),c={top:0,right:0,bottom:0,left:0},d=960,e=500,f=a.utils.defaultColor(),g=function(a){return a.x},h=function(a){return a.y},i=function(a,b){return!isNaN(h(a,b))&&h(a,b)!==null},j=function(a){return a.area},k=!1,l,m,n="linear";b.size(16).sizeDomain([16,256]);var o,p;return q.dispatch=b.dispatch,q.scatter=b,d3.rebind(q,b,"id","interactive","size","xScale","yScale","zScale","xDomain","yDomain","sizeDomain","forceX","forceY","forceSize","clipVoronoi","clipRadius","padData"),q.margin=function(a){return arguments.length?(c.top=typeof a.top!="undefined"?a.top:c.top,c.right=typeof a.right!="undefined"?a.right:c.right,c.bottom=typeof a.bottom!="undefined"?a.bottom:c.bottom,c.left=typeof a.left!="undefined"?a.left:c.left,q):c},q.width=function(a){return arguments.length?(d=a,q):d},q.height=function(a){return arguments.length?(e=a,q):e},q.x=function(a){return arguments.length?(g=a,b.x(a),q):g},q.y=function(a){return arguments.length?(h=a,b.y(a),q):h},q.clipEdge=function(a){return arguments.length?(k=a,q):k},q.color=function(c){return arguments.length?(f=a.utils.getColor(c),b.color(f),q):f},q.interpolate=function(a){return arguments.length?(n=a,q):n},q.defined=function(a){return arguments.length?(i=a,q):i},q.isArea=function(a){return arguments.length?(j=d3.functor(a),q):j},q},a.models.lineChart=function(){function s(a){return a.each(function(l){var t=d3.select(this),u=this,v=(h||parseInt(t.style("width"))||960)-f.left-f.right,w=(i||parseInt(t.style("height"))||400)-f.top-f.bottom;s.update=function(){s(a)},s.container=this;if(!l||!l.length||!l.filter(function(a){return a.values.length}).length){var z=t.selectAll(".nv-noData").data([p]);return z.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),z.attr("x",f.left+v/2).attr("y",f.top+w/2).text(function(a){return a}),s}t.selectAll(".nv-noData").remove(),m=b.xScale(),n=b.yScale();var A=t.selectAll("g.nv-wrap.nv-lineChart").data([l]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),C=A.select("g");B.append("g").attr("class","nv-x nv-axis"),B.append("g").attr("class","nv-y nv-axis"),B.append("g").attr("class","nv-linesWrap"),B.append("g").attr("class","nv-legendWrap"),j&&(e.width(v),C.select(".nv-legendWrap").datum(l).call(e),f.top!=e.height()&&(f.top=e.height(),w=(i||parseInt(t.style("height"))||400)-f.top-f.bottom),A.select(".nv-legendWrap").attr("transform","translate(0,"+ -f.top+")")),A.attr("transform","translate("+f.left+","+f.top+")"),b.width(v).height(w).color(l.map(function(a,b){return a.color||g(a,b)}).filter(function(a,b){return!l[b].disabled}));var D=C.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled}));d3.transition(D).call(b),c.scale(m).ticks(v/100).tickSize(-w,0),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),d3.transition(C.select(".nv-x.nv-axis")).call(c),d.scale(n).ticks(w/36).tickSize(-v,0),d3.transition(C.select(".nv-y.nv-axis")).call(d),e.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,l.filter(function(a){return!a.disabled}).length||l.map(function(a){return a.disabled=!1,A.selectAll(".nv-series").classed("disabled",!1),a}),o.disabled=l.map(function(a){return!!a.disabled}),q.stateChange(o),a.transition().call(s)}),q.on("tooltipShow",function(a){k&&r(a,u.parentNode)}),q.on("changeState",function(b){typeof b.disabled!="undefined"&&(l.forEach(function(a,c){a.disabled=b.disabled[c]}),o.disabled=b.disabled),a.call(s)})}),s}var b=a.models.line(),c=a.models.axis(),d=a.models.axis(),e=a.models.legend(),f={top:30,right:20,bottom:50,left:60},g=a.utils.defaultColor(),h=null,i=null,j=!0,k=!0,l=function(a,b,c,d,e){return"

"+a+"

"+"

"+c+" at "+b+"

"},m,n,o={},p="No Data Available.",q=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");c.orient("bottom").tickPadding(7),d.orient("left");var r=function(e,f){if(f){var g=d3.select(f).select("svg"),h=g.attr("viewBox");if(h){h=h.split(" ");var i=parseInt(g.style("width"))/h[2];e.pos[0]=e.pos[0]*i,e.pos[1]=e.pos[1]*i}}var j=e.pos[0]+(f.offsetLeft||0),k=e.pos[1]+(f.offsetTop||0),m=c.tickFormat()(b.x()(e.point,e.pointIndex)),n=d.tickFormat()(b.y()(e.point,e.pointIndex)),o=l(e.series.key,m,n,e,s);a.tooltip.show([j,k],o,null,null,f)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+f.left,a.pos[1]+f.top],q.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){q.tooltipHide(a)}),q.on("tooltipHide",function(){k&&a.tooltip.cleanup()}),s.dispatch=q,s.lines=b,s.legend=e,s.xAxis=c,s.yAxis=d,d3.rebind(s,b,"defined","isArea","x","y","size","xScale","yScale","xDomain","yDomain","forceX","forceY","interactive","clipEdge","clipVoronoi","id","interpolate"),s.margin=function(a){return arguments.length?(f.top=typeof a.top!="undefined"?a.top:f.top,f.right=typeof a.right!="undefined"?a.right:f.right,f.bottom=typeof a.bottom!="undefined"?a.bottom:f.bottom,f.left=typeof a.left!="undefined"?a.left:f.left,s):f},s.width=function(a){return arguments.length?(h=a,s):h},s.height=function(a){return arguments.length?(i=a,s):i},s.color=function(b){return arguments.length?(g=a.utils.getColor(b),e.color(g),s):g},s.showLegend=function(a){return arguments.length?(j=a,s):j},s.tooltips=function(a){return arguments.length?(k=a,s):k},s.tooltipContent=function(a){return arguments.length?(l=a,s):l},s.state=function(a){return arguments.length?(o=a,s):o},s.noData=function(a){return arguments.length?(p=a,s):p},s},a.models.linePlusBarChart=function(){function x(a){return a.each(function(k){var l=d3.select(this),p=this,y=(i||parseInt(l.style("width"))||960)-h.left-h.right,z=(j||parseInt(l.style("height"))||400)-h.top-h.bottom;x.update=function(){x(a)},x.container=this;if(!k||!k.length||!k.filter(function(a){return a.values.length}).length){var A=l.selectAll(".nv-noData").data([t]);return A.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),A.attr("x",h.left+y/2).attr("y",h.top+z/2).text(function(a){return a}),x}l.selectAll(".nv-noData").remove();var B=k.filter(function(a){return!a.disabled&&a.bar}),C=k.filter(function(a){return!a.bar});q=C.filter(function(a){return!a.disabled}).length&&C.filter(function(a){return!a.disabled})[0].values.length?b.xScale():c.xScale(),r=c.yScale(),s=b.yScale();var D=d3.select(this).selectAll("g.nv-wrap.nv-linePlusBar").data([k]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),F=D.select("g");E.append("g").attr("class","nv-x nv-axis"),E.append("g").attr("class","nv-y1 nv-axis"),E.append("g").attr("class","nv-y2 nv-axis"),E.append("g").attr("class","nv-barsWrap"),E.append("g").attr("class","nv-linesWrap"),E.append("g").attr("class","nv-legendWrap"),n&&(g.width(y/2),F.select(".nv-legendWrap").datum(k.map(function(a){return a.originalKey=a.originalKey===undefined?a.key:a.originalKey,a.key=a.originalKey+(a.bar?" (left axis)":" (right axis)"),a})).call(g),h.top!=g.height()&&(h.top=g.height(),z=(j||parseInt(l.style("height"))||400)-h.top-h.bottom),F.select(".nv-legendWrap").attr("transform","translate("+y/2+","+ -h.top+")")),D.attr("transform","translate("+h.left+","+h.top+")"),b.width(y).height(z).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled&&!k[b].bar})),c.width(y).height(z).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled&&k[b].bar}));var G=F.select(".nv-barsWrap").datum(B.length?B:[{values:[]}]),H=F.select(".nv-linesWrap").datum(C[0].disabled?[{values:[]}]:C);d3.transition(G).call(c),d3.transition(H).call(b),d.scale(q).ticks(y/100).tickSize(-z,0),F.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")"),d3.transition(F.select(".nv-x.nv-axis")).call(d),e.scale(r).ticks(z/36).tickSize(-y,0),d3.transition(F.select(".nv-y1.nv-axis")).style("opacity",B.length?1:0).call(e),f.scale(s).ticks(z/36).tickSize(B.length?0:-y,0),F.select(".nv-y2.nv-axis").style("opacity",C.length?1:0).attr("transform","translate("+y+",0)"),d3.transition(F.select(".nv-y2.nv-axis")).call(f),g.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,D.selectAll(".nv-series").classed("disabled",!1),a}),v.disabled=k.map(function(a){return!!a.disabled}),u.stateChange(v),a.transition().call(x)}),u.on("tooltipShow",function(a){o&&w(a,p.parentNode)}),u.on("changeState",function(b){typeof b.disabled!="undefined"&&(k.forEach(function(a,c){a.disabled=b.disabled[c]}),v.disabled=b.disabled),a.call(x)})}),x}var b=a.models.line(),c=a.models.historicalBar(),d=a.models.axis(),e=a.models.axis(),f=a.models.axis(),g=a.models.legend(),h={top:30,right:60,bottom:50,left:60},i=null,j=null,k=function(a){return a.x},l=function(a){return a.y},m=a.utils.defaultColor(),n=!0,o=!0,p=function(a,b,c,d,e){return"

"+a+"

"+"

"+c+" at "+b+"

"},q,r,s,t="No Data Available.",u=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");c.padData(!0),b.clipEdge(!1).padData(!0),d.orient("bottom").tickPadding(7).highlightZero(!1),e.orient("left"),f.orient("right");var v={},w=function(c,g){var h=c.pos[0]+(g.offsetLeft||0),i=c.pos[1]+(g.offsetTop||0),j=d.tickFormat()(b.x()(c.point,c.pointIndex)),k=(c.series.bar?e:f).tickFormat()(b.y()(c.point,c.pointIndex)),l=p(c.series.key,j,k,c,x);a.tooltip.show([h,i],l,c.value<0?"n":"s",null,g)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+h.left,a.pos[1]+h.top],u.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){u.tooltipHide(a)}),c.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+h.left,a.pos[1]+h.top],u.tooltipShow(a)}),c.dispatch.on("elementMouseout.tooltip",function(a){u.tooltipHide(a)}),u.on("tooltipHide",function(){o&&a.tooltip.cleanup()}),x.dispatch=u,x.legend=g,x.lines=b,x.bars=c,x.xAxis=d,x.y1Axis=e,x.y2Axis=f,d3.rebind(x,b,"defined","size","clipVoronoi","interpolate"),x.x=function(a){return arguments.length?(k=a,b.x(a),c.x(a),x):k},x.y=function(a){return arguments.length?(l=a,b.y(a),c.y(a),x):l},x.margin=function(a){return arguments.length?(h.top=typeof a.top!="undefined"?a.top:h.top,h.right=typeof a.right!="undefined"?a.right:h.right,h.bottom=typeof a.bottom!="undefined"?a.bottom:h.bottom,h.left=typeof a.left!="undefined"?a.left:h.left,x):h},x.width=function(a){return arguments.length?(i=a,x):i},x.height=function(a){return arguments.length?(j=a,x):j},x.color=function(b){return arguments.length?(m=a.utils.getColor(b),g.color(m),x):m},x.showLegend=function(a){return arguments.length?(n=a,x):n},x.tooltips=function(a){return arguments.length?(o=a,x):o},x.tooltipContent=function(a){return arguments.length?(p=a,x):p},x.state=function(a){return arguments.length?(v=a,x):v},x.noData=function(a){return arguments.length?(t=a,x):t},x},a.models.lineWithFocusChart=function(){function A(a){return a.each(function(w){function P(a){var b=+(a=="e"),c=b?1:-1,d=F/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"Z"+"M"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function Q(){i.empty()||i.extent(u),N.data([i.empty()?r.domain():u]).each(function(a,b){var c=r(a[0])-p.range()[0],d=p.range()[1]-r(a[1]);d3.select(this).select(".left").attr("width",c<0?0:c),d3.select(this).select(".right").attr("x",r(a[1])).attr("width",d<0?0:d)})}function R(){u=i.empty()?null:i.extent(),extent=i.empty()?r.domain():i.extent(),y.brush({extent:extent,brush:i}),Q();var a=J.select(".nv-focus .nv-linesWrap").datum(w.filter(function(a){return!a.disabled}).map(function(a,c){return{key:a.key,values:a.values.filter(function(a,c){return b.x()(a,c)>=extent[0]&&b.x()(a,c)<=extent[1]})}}));d3.transition(a).call(b),d3.transition(J.select(".nv-focus .nv-x.nv-axis")).call(d),d3.transition(J.select(".nv-focus .nv-y.nv-axis")).call(e)}var B=d3.select(this),C=this,D=(m||parseInt(B.style("width"))||960)-j.left-j.right,E=(n||parseInt(B.style("height"))||400)-j.top-j.bottom-o,F=o-k.top-k.bottom;A.update=function(){A(a)},A.container=this;if(!w||!w.length||!w.filter(function(a){return a.values.length}).length){var G=B.selectAll(".nv-noData").data([x]);return G.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),G.attr("x",j.left+D/2).attr("y",j.top+E/2).text(function(a){return a}),A}B.selectAll(".nv-noData").remove(),p=b.xScale(),q=b.yScale(),r=c.xScale(),s=c.yScale();var H=B.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([w]),I=H.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),J=H.select("g");I.append("g").attr("class","nv-legendWrap");var K=I.append("g").attr("class","nv-focus");K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-linesWrap");var L=I.append("g").attr("class","nv-context");L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-linesWrap"),L.append("g").attr("class","nv-brushBackground"),L.append("g").attr("class","nv-x nv-brush"),t&&(h.width(D),J.select(".nv-legendWrap").datum(w).call(h),j.top!=h.height()&&(j.top=h.height(),E=(n||parseInt(B.style("height"))||400)-j.top-j.bottom-o),J.select(".nv-legendWrap").attr("transform","translate(0,"+ -j.top+")")),H.attr("transform","translate("+j.left+","+j.top+")"),b.width(D).height(E).color(w.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!w[b].disabled})),c.defined(b.defined()).width(D).height(F).color(w.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!w[b].disabled})),J.select(".nv-context").attr("transform","translate(0,"+(E+j.bottom+k.top)+")");var M=J.select(".nv-context .nv-linesWrap").datum(w.filter(function(a){return!a.disabled}));d3.transition(M).call(c),d.scale(p).ticks(D/100).tickSize(-E,0),e.scale(q).ticks(E/36).tickSize(-D,0),J.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+E+")"),i.x(r).on("brush",R),u&&i.extent(u);var N=J.select(".nv-brushBackground").selectAll("g").data([u||i.extent()]),O=N.enter().append("g");O.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",F),O.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",F),gBrush=J.select(".nv-x.nv-brush").call(i),gBrush.selectAll("rect").attr("height",F),gBrush.selectAll(".resize").append("path").attr("d",P),R(),f.scale(r).ticks(D/100).tickSize(-F,0),J.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+s.range()[0]+")"),d3.transition(J.select(".nv-context .nv-x.nv-axis")).call(f),g.scale(s).ticks(F/36).tickSize(-D,0),d3.transition(J.select(".nv-context .nv-y.nv-axis")).call(g),J.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+s.range()[0]+")"),h.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,w.filter(function(a){return!a.disabled}).length||w.map(function(a){return a.disabled=!1,H.selectAll(".nv-series").classed("disabled",!1),a}),a.transition().call(A)}),y.on("tooltipShow",function(a){v&&z(a,C.parentNode)})}),A}var b=a.models.line(),c=a.models.line(),d=a.models.axis(),e=a.models.axis(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=d3.svg.brush(),j={top:30,right:30,bottom:30,left:60},k={top:0,right:30,bottom:20,left:60},l=a.utils.defaultColor(),m=null,n=null,o=100,p,q,r,s,t=!0,u=null,v=!0,w=function(a,b,c,d,e){return"

"+a+"

"+"

"+c+" at "+b+"

"},x="No Data Available.",y=d3.dispatch("tooltipShow","tooltipHide","brush");b.clipEdge(!0),c.interactive(!1),d.orient("bottom").tickPadding(5),e.orient("left"),f.orient("bottom").tickPadding(5),g.orient("left");var z=function(c,f){var g=c.pos[0]+(f.offsetLeft||0),h=c.pos[1]+(f.offsetTop||0),i=d.tickFormat()(b.x()(c.point,c.pointIndex)),j=e.tickFormat()(b.y()(c.point,c.pointIndex)),k=w(c.series.key,i,j,c,A);a.tooltip.show([g,h],k,null,null,f)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+j.left,a.pos[1]+j.top],y.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){y.tooltipHide(a)}),y.on("tooltipHide",function(){v&&a.tooltip.cleanup()}),A.dispatch=y,A.legend=h,A.lines=b,A.lines2=c,A.xAxis=d,A.yAxis=e,A.x2Axis=f,A.y2Axis=g,d3.rebind(A,b,"defined","isArea","size","xDomain","yDomain","forceX","forceY","interactive","clipEdge","clipVoronoi","id"),A.x=function(a){return arguments.length?(b.x(a),c.x(a),A):b.x},A.y=function(a){return arguments.length?(b.y(a),c.y(a),A):b.y},A.margin=function(a){return arguments.length?(j.top=typeof a.top!="undefined"?a.top:j.top,j.right=typeof a.right!="undefined"?a.right:j.right,j.bottom=typeof a.bottom!="undefined"?a.bottom:j.bottom,j.left=typeof a.left!="undefined"?a.left:j.left,A):j},A.margin2=function(a){return arguments.length?(k=a,A):k},A.width=function(a){return arguments.length?(m=a,A):m},A.height=function(a){return arguments.length?(n=a,A):n},A.height2=function(a){return arguments.length?(o=a,A):o},A.color=function(b){return arguments.length?(l=a.utils.getColor(b),h.color(l),A):l},A.showLegend=function(a){return arguments.length?(t=a,A):t},A.tooltips=function( +a){return arguments.length?(v=a,A):v},A.tooltipContent=function(a){return arguments.length?(w=a,A):w},A.interpolate=function(a){return arguments.length?(b.interpolate(a),c.interpolate(a),A):b.interpolate()},A.noData=function(a){return arguments.length?(x=a,A):x},A.xTickFormat=function(a){return arguments.length?(d.tickFormat(a),f.tickFormat(a),A):d.tickFormat()},A.yTickFormat=function(a){return arguments.length?(e.tickFormat(a),g.tickFormat(a),A):e.tickFormat()},A},a.models.multiBar=function(){function t(a){return a.each(function(a){var t=c-b.left-b.right,u=d-b.top-b.bottom,v=d3.select(this);l&&(a=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(i)(a)),a=a.map(function(a,b){return a.values=a.values.map(function(a){return a.series=b,a}),a});var w=o&&p?[]:a.map(function(a){return a.values.map(function(a,b){return{x:h(a,b),y:i(a,b),y0:a.y0}})});e.domain(d3.merge(w).map(function(a){return a.x})).rangeBands([0,t],.1),f.domain(p||d3.extent(d3.merge(w).map(function(a){return a.y+(l?a.y0:0)}).concat(j))).range([u,0]);if(e.domain()[0]===e.domain()[1]||f.domain()[0]===f.domain()[1])singlePoint=!0;e.domain()[0]===e.domain()[1]&&(e.domain()[0]?e.domain([e.domain()[0]-e.domain()[0]*.01,e.domain()[1]+e.domain()[1]*.01]):e.domain([-1,1])),f.domain()[0]===f.domain()[1]&&(f.domain()[0]?f.domain([f.domain()[0]+f.domain()[0]*.01,f.domain()[1]-f.domain()[1]*.01]):f.domain([-1,1])),r=r||e,s=s||f;var z=v.selectAll("g.nv-wrap.nv-multibar").data([a]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-groups"),z.attr("transform","translate("+b.left+","+b.top+")"),B.append("clipPath").attr("id","nv-edge-clip-"+g).append("rect"),z.select("#nv-edge-clip-"+g+" rect").attr("width",t).attr("height",u),D.attr("clip-path",k?"url(#nv-edge-clip-"+g+")":"");var E=z.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});E.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(E.exit()).selectAll("rect.nv-bar").delay(function(b,c){return c*n/a[0].values.length}).attr("y",function(a){return l?s(a.y0):s(0)}).attr("height",0).remove(),E.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return m(a,b)}).style("stroke",function(a,b){return m(a,b)}),d3.transition(E).style("stroke-opacity",1).style("fill-opacity",.75);var F=E.selectAll("rect.nv-bar").data(function(a){return a.values});F.exit().remove();var G=F.enter().append("rect").attr("class",function(a,b){return i(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(b,c,d){return l?0:d*e.rangeBand()/a.length}).attr("y",function(a){return s(l?a.y0:0)}).attr("height",0).attr("width",e.rangeBand()/(l?1:a.length));F.style("fill",function(a,b,c){return m(a,c,b)}).style("stroke",function(a,b,c){return m(a,c,b)}).on("mouseover",function(b,c){d3.select(this).classed("hover",!0),q.elementMouseover({value:i(b,c),point:b,series:a[b.series],pos:[e(h(b,c))+e.rangeBand()*(l?a.length/2:b.series+.5)/a.length,f(i(b,c)+(l?b.y0:0))],pointIndex:c,seriesIndex:b.series,e:d3.event})}).on("mouseout",function(b,c){d3.select(this).classed("hover",!1),q.elementMouseout({value:i(b,c),point:b,series:a[b.series],pointIndex:c,seriesIndex:b.series,e:d3.event})}).on("click",function(b,c){q.elementClick({value:i(b,c),point:b,series:a[b.series],pos:[e(h(b,c))+e.rangeBand()*(l?a.length/2:b.series+.5)/a.length,f(i(b,c)+(l?b.y0:0))],pointIndex:c,seriesIndex:b.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(b,c){q.elementDblClick({value:i(b,c),point:b,series:a[b.series],pos:[e(h(b,c))+e.rangeBand()*(l?a.length/2:b.series+.5)/a.length,f(i(b,c)+(l?b.y0:0))],pointIndex:c,seriesIndex:b.series,e:d3.event}),d3.event.stopPropagation()}),F.attr("class",function(a,b){return i(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+e(h(a,b))+",0)"}),l?d3.transition(F).delay(function(b,c){return c*n/a[0].values.length}).attr("y",function(a,b){return f(i(a,b)+(l?a.y0:0))}).attr("height",function(a,b){return Math.max(Math.abs(f(a.y+(l?a.y0:0))-f(l?a.y0:0)),1)}).each("end",function(){d3.transition(d3.select(this)).attr("x",function(b,c){return l?0:b.series*e.rangeBand()/a.length}).attr("width",e.rangeBand()/(l?1:a.length))}):d3.transition(F).delay(function(b,c){return c*n/a[0].values.length}).attr("x",function(b,c){return b.series*e.rangeBand()/a.length}).attr("width",e.rangeBand()/a.length).each("end",function(){d3.transition(d3.select(this)).attr("y",function(a,b){return i(a,b)<0?f(0):f(0)-f(i(a,b))<1?f(0)-1:f(i(a,b))}).attr("height",function(a,b){return Math.max(Math.abs(f(i(a,b))-f(0)),1)})}),r=e.copy(),s=f.copy()}),t}var b={top:0,right:0,bottom:0,left:0},c=960,d=500,e=d3.scale.ordinal(),f=d3.scale.linear(),g=Math.floor(Math.random()*1e4),h=function(a){return a.x},i=function(a){return a.y},j=[0],k=!0,l=!1,m=a.utils.defaultColor(),n=1200,o,p,q=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),r,s;return t.dispatch=q,t.x=function(a){return arguments.length?(h=a,t):h},t.y=function(a){return arguments.length?(i=a,t):i},t.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,t):b},t.width=function(a){return arguments.length?(c=a,t):c},t.height=function(a){return arguments.length?(d=a,t):d},t.xScale=function(a){return arguments.length?(e=a,t):e},t.yScale=function(a){return arguments.length?(f=a,t):f},t.xDomain=function(a){return arguments.length?(o=a,t):o},t.yDomain=function(a){return arguments.length?(p=a,t):p},t.forceY=function(a){return arguments.length?(j=a,t):j},t.stacked=function(a){return arguments.length?(l=a,t):l},t.clipEdge=function(a){return arguments.length?(k=a,t):k},t.color=function(b){return arguments.length?(m=a.utils.getColor(b),t):m},t.id=function(a){return arguments.length?(g=a,t):g},t.delay=function(a){return arguments.length?(n=a,t):n},t},a.models.multiBarChart=function(){function w(a){return a.each(function(p){var z=d3.select(this),A=this,B=(h||parseInt(z.style("width"))||960)-g.left-g.right,C=(i||parseInt(z.style("height"))||400)-g.top-g.bottom;w.update=function(){a.transition().call(w)},w.container=this;if(!p||!p.length||!p.filter(function(a){return a.values.length}).length){var D=z.selectAll(".nv-noData").data([t]);return D.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),D.attr("x",g.left+B/2).attr("y",g.top+C/2).text(function(a){return a}),w}z.selectAll(".nv-noData").remove(),q=b.xScale(),r=b.yScale();var E=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([p]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-controlsWrap"),l&&(e.width(B/2),G.select(".nv-legendWrap").datum(p).call(e),g.top!=e.height()&&(g.top=e.height(),C=(i||parseInt(z.style("height"))||400)-g.top-g.bottom),G.select(".nv-legendWrap").attr("transform","translate("+B/2+","+ -g.top+")"));if(k){var H=[{key:"Grouped",disabled:b.stacked()},{key:"Stacked",disabled:!b.stacked()}];f.width(180).color(["#444","#444","#444"]),G.select(".nv-controlsWrap").datum(H).attr("transform","translate(0,"+ -g.top+")").call(f)}E.attr("transform","translate("+g.left+","+g.top+")"),b.width(B).height(C).color(p.map(function(a,b){return a.color||j(a,b)}).filter(function(a,b){return!p[b].disabled}));var I=G.select(".nv-barsWrap").datum(p.filter(function(a){return!a.disabled}));d3.transition(I).call(b),c.scale(q).ticks(B/100).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")"),d3.transition(G.select(".nv-x.nv-axis")).call(c);var J=G.select(".nv-x.nv-axis > g").selectAll("g");J.selectAll("line, text").style("opacity",1),m&&J.filter(function(a,b){return b%Math.ceil(p[0].values.length/(B/100))!==0}).selectAll("text, line").style("opacity",0),n&&J.selectAll("text").attr("transform",function(a,b,c){return"rotate("+n+" 0,0)"}).attr("text-transform",n>0?"start":"end"),G.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1),d.scale(r).ticks(C/36).tickSize(-B,0),d3.transition(G.select(".nv-y.nv-axis")).call(d),e.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,p.filter(function(a){return!a.disabled}).length||p.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),s.disabled=p.map(function(a){return!!a.disabled}),u.stateChange(s),a.transition().call(w)}),f.dispatch.on("legendClick",function(c,d){if(!c.disabled)return;H=H.map(function(a){return a.disabled=!0,a}),c.disabled=!1;switch(c.key){case"Grouped":b.stacked(!1);break;case"Stacked":b.stacked(!0)}s.stacked=b.stacked(),u.stateChange(s),a.transition().call(w)}),u.on("tooltipShow",function(a){o&&v(a,A.parentNode)}),u.on("changeState",function(c){typeof c.disabled!="undefined"&&(p.forEach(function(a,b){a.disabled=c.disabled[b]}),s.disabled=c.disabled),typeof c.stacked!="undefined"&&(b.stacked(c.stacked),s.stacked=c.stacked),a.call(w)})}),w}var b=a.models.multiBar(),c=a.models.axis(),d=a.models.axis(),e=a.models.legend(),f=a.models.legend(),g={top:30,right:20,bottom:30,left:60},h=null,i=null,j=a.utils.defaultColor(),k=!0,l=!0,m=!0,n=0,o=!0,p=function(a,b,c,d,e){return"

"+a+"

"+"

"+c+" on "+b+"

"},q,r,s={stacked:!1},t="No Data Available.",u=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");b.stacked(!1),c.orient("bottom").tickPadding(7).highlightZero(!1).showMaxMin(!1).tickFormat(function(a){return a}),d.orient("left").tickFormat(d3.format(",.1f"));var v=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),i=c.tickFormat()(b.x()(e.point,e.pointIndex)),j=d.tickFormat()(b.y()(e.point,e.pointIndex)),k=p(e.series.key,i,j,e,w);a.tooltip.show([g,h],k,e.value<0?"n":"s",null,f)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+g.left,a.pos[1]+g.top],u.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){u.tooltipHide(a)}),u.on("tooltipHide",function(){o&&a.tooltip.cleanup()}),w.dispatch=u,w.multibar=b,w.legend=e,w.xAxis=c,w.yAxis=d,d3.rebind(w,b,"x","y","xDomain","yDomain","forceX","forceY","clipEdge","id","stacked","delay"),w.margin=function(a){return arguments.length?(g.top=typeof a.top!="undefined"?a.top:g.top,g.right=typeof a.right!="undefined"?a.right:g.right,g.bottom=typeof a.bottom!="undefined"?a.bottom:g.bottom,g.left=typeof a.left!="undefined"?a.left:g.left,w):g},w.width=function(a){return arguments.length?(h=a,w):h},w.height=function(a){return arguments.length?(i=a,w):i},w.color=function(b){return arguments.length?(j=a.utils.getColor(b),e.color(j),w):j},w.showControls=function(a){return arguments.length?(k=a,w):k},w.showLegend=function(a){return arguments.length?(l=a,w):l},w.reduceXTicks=function(a){return arguments.length?(m=a,w):m},w.rotateLabels=function(a){return arguments.length?(n=a,w):n},w.tooltip=function(a){return arguments.length?(p=a,w):p},w.tooltips=function(a){return arguments.length?(o=a,w):o},w.tooltipContent=function(a){return arguments.length?(p=a,w):p},w.state=function(a){return arguments.length?(s=a,w):s},w.noData=function(a){return arguments.length?(t=a,w):t},w},a.models.multiBarHorizontal=function(){function v(a){return a.each(function(a){var e=c-b.left-b.right,p=d-b.top-b.bottom,v=d3.select(this);l&&(a=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(i)(a)),a=a.map(function(a,b){return a.values=a.values.map(function(a){return a.series=b,a}),a});var w=q&&r?[]:a.map(function(a){return a.values.map(function(a,b){return{x:h(a,b),y:i(a,b),y0:a.y0}})});f.domain(q||d3.merge(w).map(function(a){return a.x})).rangeBands([0,p],.1),g.domain(r||d3.extent(d3.merge(w).map(function(a){return a.y+(l?a.y0:0)}).concat(j))),m&&!l?g.range([g.domain()[0]<0?n:0,e-(g.domain()[1]>0?n:0)]):g.range([0,e]),t=t||f,u=u||d3.scale.linear().domain(g.domain()).range([g(0),g(0)]);var z=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([a]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-groups"),z.attr("transform","translate("+b.left+","+b.top+")");var E=z.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});E.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(E.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),E.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),d3.transition(E).style("stroke-opacity",1).style("fill-opacity",.75);var F=E.selectAll("g.nv-bar").data(function(a){return a.values});F.exit().remove();var G=F.enter().append("g").attr("transform",function(b,c,d){return"translate("+u(l?b.y0:0)+","+(l?0:d*f.rangeBand()/a.length+f(h(b,c)))+")"});G.append("rect").attr("width",0).attr("height",f.rangeBand()/(l?1:a.length)),F.on("mouseover",function(b,c){d3.select(this).classed("hover",!0),s.elementMouseover({value:i(b,c),point:b,series:a[b.series],pos:[g(i(b,c)+(l?b.y0:0)),f(h(b,c))+f.rangeBand()*(l?a.length/2:b.series+.5)/a.length],pointIndex:c,seriesIndex:b.series,e:d3.event})}).on("mouseout",function(b,c){d3.select(this).classed("hover",!1),s.elementMouseout({value:i(b,c),point:b,series:a[b.series],pointIndex:c,seriesIndex:b.series,e:d3.event})}).on("click",function(b,c){s.elementClick({value:i(b,c),point:b,series:a[b.series],pos:[f(h(b,c))+f.rangeBand()*(l?a.length/2:b.series+.5)/a.length,g(i(b,c)+(l?b.y0:0))],pointIndex:c,seriesIndex:b.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(b,c){s.elementDblClick({value:i(b,c),point:b,series:a[b.series],pos:[f(h(b,c))+f.rangeBand()*(l?a.length/2:b.series+.5)/a.length,g(i(b,c)+(l?b.y0:0))],pointIndex:c,seriesIndex:b.series,e:d3.event}),d3.event.stopPropagation()}),m&&!l?(G.append("text").attr("text-anchor",function(a,b){return i(a,b)<0?"end":"start"}),F.select("text").attr("y",f.rangeBand()/2).attr("dy","-.32em").text(function(a,b){return o(i(a,b))}),d3.transition(F).select("text").attr("x",function(a,b){return i(a,b)<0?-4:g(i(a,b))-g(0)+4})):F.selectAll("text").remove(),F.attr("class",function(a,b){return i(a,b)<0?"nv-bar negative":"nv-bar positive"}),l?d3.transition(F).attr("transform",function(a,b){return"translate("+g(a.y0)+","+f(h(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(g(i(a,b)+a.y0)-g(a.y0))}).attr("height",f.rangeBand()):d3.transition(F).attr("transform",function(b,c){return"translate("+(i(b,c)<0?g(i(b,c)):g(0))+","+(b.series*f.rangeBand()/a.length+f(h(b,c)))+")"}).select("rect").attr("height",f.rangeBand()/a.length).attr("width",function(a,b){return Math.max(Math.abs(g(i(a,b))-g(0)),1)}),t=f.copy(),u=g.copy()}),v}var b={top:0,right:0,bottom:0,left:0},c=960,d=500,e=Math.floor(Math.random()*1e4),f=d3.scale.ordinal(),g=d3.scale.linear(),h=function(a){return a.x},i=function(a){return a.y},j=[0],k=a.utils.defaultColor(),l=!1,m=!1,n=60,o=d3.format(",.2f"),p=1200,q,r,s=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),t,u;return v.dispatch=s,v.x=function(a){return arguments.length?(h=a,v):h},v.y=function(a){return arguments.length?(i=a,v):i},v.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,v):b},v.width=function(a){return arguments.length?(c=a,v):c},v.height=function(a){return arguments.length?(d=a,v):d},v.xScale=function(a){return arguments.length?(f=a,v):f},v.yScale=function(a){return arguments.length?(g=a,v):g},v.xDomain=function(a){return arguments.length?(q=a,v):q},v.yDomain=function(a){return arguments.length?(r=a,v):r},v.forceY=function(a){return arguments.length?(j=a,v):j},v.stacked=function(a){return arguments.length?(l=a,v):l},v.color=function(b){return arguments.length?(k=a.utils.getColor(b),v):k},v.id=function(a){return arguments.length?(e=a,v):e},v.delay=function(a){return arguments.length?(p=a,v):p},v.showValues=function(a){return arguments.length?(m=a,v):m},v.valueFormat=function(a){return arguments.length?(o=a,v):o},v.valuePadding=function(a){return arguments.length?(n=a,v):n},v},a.models.multiBarHorizontalChart=function(){function v(a){return a.each(function(m){var o=d3.select(this),w=this,z=(h||parseInt(o.style("width"))||960)-g.left-g.right,A=(i||parseInt(o.style("height"))||400)-g.top-g.bottom;v.update=function(){a.transition().call(v)},v.container=this;if(!m||!m.length||!m.filter(function(a){return a.values.length}).length){var B=o.selectAll(".nv-noData").data([s]);return B.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),B.attr("x",g.left+z/2).attr("y",g.top+A/2).text(function(a){return a}),v}o.selectAll(".nv-noData").remove(),p=b.xScale(),q=b.yScale();var C=o.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([m]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),E=C.select("g");D.append("g").attr("class","nv-x nv-axis"),D.append("g").attr("class","nv-y nv-axis"),D.append("g").attr("class","nv-barsWrap"),D.append("g").attr("class","nv-legendWrap"),D.append("g").attr("class","nv-controlsWrap"),l&&(e.width(z/2),E.select(".nv-legendWrap").datum(m).call(e),g.top!=e.height()&&(g.top=e.height(),A=(i||parseInt(o.style("height"))||400)-g.top-g.bottom),E.select(".nv-legendWrap").attr("transform","translate("+z/2+","+ -g.top+")"));if(k){var F=[{key:"Grouped",disabled:b.stacked()},{key:"Stacked",disabled:!b.stacked()}];f.width(180).color(["#444","#444","#444"]),E.select(".nv-controlsWrap").datum(F).attr("transform","translate(0,"+ -g.top+")").call(f)}C.attr("transform","translate("+g.left+","+g.top+")"),b.width(z).height(A).color(m.map(function(a,b){return a.color||j(a,b)}).filter(function(a,b){return!m[b].disabled}));var G=E.select(".nv-barsWrap").datum(m.filter(function(a){return!a.disabled}));d3.transition(G).call(b),c.scale(p).ticks(A/24).tickSize(-z,0),d3.transition(E.select(".nv-x.nv-axis")).call(c);var H=E.select(".nv-x.nv-axis").selectAll("g");H.selectAll("line, text").style("opacity",1),d.scale(q).ticks(z/100).tickSize(-A,0),E.select(".nv-y.nv-axis").attr("transform","translate(0,"+A+")"),d3.transition(E.select(".nv-y.nv-axis")).call(d),e.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,m.filter(function(a){return!a.disabled}).length||m.map(function(a){return a.disabled=!1,C.selectAll(".nv-series").classed("disabled",!1),a}),r.disabled=m.map(function(a){return!!a.disabled}),t.stateChange(r),a.transition().call(v)}),f.dispatch.on("legendClick",function(c,d){if(!c.disabled)return;F=F.map(function(a){return a.disabled=!0,a}),c.disabled=!1;switch(c.key){case"Grouped":b.stacked(!1);break;case"Stacked":b.stacked(!0)}r.stacked=b.stacked(),t.stateChange(r),a.transition().call(v)}),t.on("tooltipShow",function(a){n&&u(a,w.parentNode)}),t.on("changeState",function(c){typeof c.disabled!="undefined"&&(m.forEach(function(a,b){a.disabled=c.disabled[b]}),r.disabled=c.disabled),typeof c.stacked!="undefined"&&(b.stacked(c.stacked),r.stacked=c.stacked),a.call(v)})}),v}var b=a.models.multiBarHorizontal(),c=a.models.axis(),d=a.models.axis(),e=a.models.legend().height(30),f=a.models.legend().height(30),g={top:30,right:20,bottom:50,left:60},h=null,i=null,j=a.utils.defaultColor(),k=!0,l=!0,m=!1,n=!0,o=function(a,b,c,d,e){return"

"+a+" - "+b+"

"+"

"+c+"

"},p,q,r={stacked:m},s="No Data Available.",t=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");b.stacked(m),c.orient("left").tickPadding(5).highlightZero(!1).showMaxMin(!1).tickFormat(function(a){return a}),d.orient("bottom").tickFormat(d3.format(",.1f"));var u=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),i=c.tickFormat()(b.x()(e.point,e.pointIndex)),j=d.tickFormat()(b.y()(e.point,e.pointIndex)),k=o(e.series.key,i,j,e,v);a.tooltip.show([g,h],k,e.value<0?"e":"w",null,f)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+g.left,a.pos[1]+g.top],t.tooltipShow(a)}),b.dispatch.on("elementMouseout.tooltip",function(a){t.tooltipHide(a)}),t.on("tooltipHide",function(){n&&a.tooltip.cleanup()}),v.dispatch=t,v.multibar=b,v.legend=e,v.xAxis=c,v.yAxis=d,d3.rebind(v,b,"x","y","xDomain","yDomain","forceX","forceY","clipEdge","id","delay","showValues","valueFormat","stacked"),v.margin=function(a){return arguments.length?(g.top=typeof a.top!="undefined"?a.top:g.top,g.right=typeof a.right!="undefined"?a.right:g.right,g.bottom=typeof a.bottom!="undefined"?a.bottom:g.bottom,g.left=typeof a.left!="undefined"?a.left:g.left,v):g},v.width=function(a){return arguments.length?(h=a,v):h},v.height=function(a){return arguments.length?(i=a,v):i},v.color=function(b){return arguments.length?(j=a.utils.getColor(b),e.color(j),v):j},v.showControls=function(a){return arguments.length?(k=a,v):k},v.showLegend=function(a){return arguments.length?(l=a,v):l},v.tooltip=function(a){return arguments.length?(o=a,v):o},v.tooltips=function(a){return arguments.length?(n=a,v):n},v.tooltipContent=function(a){return arguments.length?(o=a,v):o},v.state=function(a){return arguments.length?(r=a,v):r},v.noData=function(a){return arguments.length?(s=a,v):s},v},a.models.multiChart=function(){function y(a){return a.each(function(h){var j=d3.select(this),z=this,A=(d||parseInt(j.style("width"))||960)-b.left-b.right,B=(e||parseInt(j.style("height"))||400)-b.top-b.bottom,C=h.filter(function(a){return!a.disabled&&a.type=="line"&&a.yAxis==1}),D=h.filter(function(a){return!a.disabled&&a.type=="line"&&a.yAxis==2}),E=h.filter(function(a){return!a.disabled&&a.type=="bar"&&a.yAxis==1}),F=h.filter(function(a){return!a.disabled&&a.type=="bar"&&a.yAxis==2}),G=h.filter(function(a){return!a.disabled&&a.type=="area"&&a.yAxis==1}),H=h.filter(function(a){return!a.disabled&&a.type=="area"&&a.yAxis==2}),I=h.filter(function(a){return!a.disabled&&a.yAxis==1}).map(function(a){return a.values.map(function(a,b){return{x:a.x,y:a.y}})}),J=h.filter(function(a){return!a.disabled&&a.yAxis==2}).map(function(a){return a.values.map(function(a,b){return{x:a.x,y:a.y}})});i.domain(d3.extent(d3.merge(I.concat(J)),function(a){return a.x})).range([0,A]);var K=j.selectAll("g.wrap.multiChart").data([h]),L=K.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");L.append("g").attr("class","x axis"),L.append("g").attr("class","y1 axis"),L.append("g").attr("class","y2 axis"),L.append("g").attr("class","lines1Wrap"),L.append("g").attr("class","lines2Wrap"),L.append("g").attr("class","bars1Wrap"),L.append("g").attr("class","bars2Wrap"),L.append("g").attr("class","stack1Wrap"),L.append("g").attr("class","stack2Wrap"),L.append("g").attr("class","legendWrap");var M=K.select("g");f&&(v.width(A/2),M.select(".legendWrap").datum(h.map(function(a){return a.originalKey=a.originalKey===undefined?a.key:a.originalKey,a.key=a.originalKey+(a.yAxis==1?"":" (right axis)"),a})).call(v),b.top!=v.height()&&(b.top=v.height(),B=(e||parseInt(j.style("height"))||400)-b.top-b.bottom),M.select(".legendWrap").attr("transform","translate("+A/2+","+ -b.top+")")),m.width(A).height(B).interpolate("monotone").color(h.map(function(a,b){return a.color||c[b%c.length]}).filter(function(a,b){return!h[b].disabled&&h[b].yAxis==1&&h[b].type=="line"})),n.width(A).height(B).interpolate("monotone").color(h.map(function(a,b){return a.color||c[b%c.length]}).filter(function(a,b){return!h[b].disabled&&h[b].yAxis==2&&h[b].type=="line"})),o.width(A).height(B).color(h.map(function(a,b){return a.color||c[b%c.length]}).filter(function(a,b){return!h[b].disabled&&h[b].yAxis==1&&h[b].type=="bar"})),p.width(A).height(B).color(h.map(function(a,b){return a.color||c[b%c.length]}).filter(function(a,b){return!h[b].disabled&&h[b].yAxis==2&&h[b].type=="bar"})),q.width(A).height(B).color(h.map(function(a,b){return a.color||c[b%c.length]}).filter(function(a,b){return!h[b].disabled&&h[b].yAxis==1&&h[b].type=="area"})),r.width(A).height(B).color(h.map(function(a,b){return a.color||c[b%c.length]}).filter(function(a,b){return!h[b].disabled&&h[b].yAxis==2&&h[b].type=="area"})),M.attr("transform","translate("+b.left+","+b.top+")");var N=M.select(".lines1Wrap").datum(C),O=M.select(".bars1Wrap").datum(E),P=M.select(".stack1Wrap").datum(G),Q=M.select(".lines2Wrap").datum(D),R=M.select(".bars2Wrap").datum(F),S=M.select(".stack2Wrap").datum(H),T=G.length?G.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],U=H.length?H.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];k.domain(d3.extent(d3.merge(I).concat(T),function(a){return a.y})).range([0,B]),l.domain(d3.extent(d3.merge(J).concat(U),function(a){return a.y})).range([0,B]),m.yDomain(k.domain()),o.yDomain(k.domain()),q.yDomain(k.domain()),n.yDomain(l.domain()),p.yDomain(l.domain()),r.yDomain(l.domain()),G.length&&d3.transition(P).call(q),H.length&&d3.transition(S).call(r),E.length&&d3.transition(O).call(o),F.length&&d3.transition(R).call(p),C.length&&d3.transition(N).call(m),D.length&&d3.transition(Q).call(n),s.ticks(A/100).tickSize(-B,0),M.select(".x.axis").attr("transform","translate(0,"+B+")"),d3.transition(M.select(".x.axis")).call(s),t.ticks(B/36).tickSize(-A,0),d3.transition(M.select(".y1.axis")).call(t),u.ticks(B/36).tickSize(-A,0),d3.transition(M.select(".y2.axis")).call(u),M.select(".y2.axis").style("opacity",J.length?1:0).attr("transform","translate("+i.range()[1]+",0)"),v.dispatch.on("legendClick",function(b,c){b.disabled=!b.disabled,h.filter(function(a){return!a.disabled}).length||h.map(function(a){return a.disabled=!1,K.selectAll(".series").classed("disabled",!1),a}),a.transition().call(y)}),w.on("tooltipShow",function(a){g&&x(a,z.parentNode)})}),y.update=function(){y(a)},y.container=this,y}var b={top:30,right:20,bottom:50,left:60},c=d3.scale.category20().range(),d=null,e=null,f=!0,g=!0,h=function(a,b,c,d,e){return"

"+a+"

"+"

"+c+" at "+b+"

"},i,j,i=d3.scale.linear(),k=d3.scale.linear(),l=d3.scale.linear(),m=a.models.line().yScale(k),n=a.models.line().yScale(l),o=a.models.multiBar().stacked(!1).yScale(k),p=a.models.multiBar().stacked(!1).yScale(l),q=a.models.stackedArea().yScale(k),r=a.models.stackedArea().yScale(l),s=a.models.axis().scale(i).orient("bottom").tickPadding(5),t=a.models.axis().scale(k).orient("left"),u=a.models.axis().scale(l).orient("right"),v=a.models.legend().height(30),w=d3.dispatch("tooltipShow","tooltipHide"),x=function(b,c){var d=b.pos[0]+(c.offsetLeft||0),e=b.pos[1]+(c.offsetTop||0),f=s.tickFormat()(m.x()(b.point,b.pointIndex)),g=(b.series.bar?t:u).tickFormat()(m.y()(b.point,b.pointIndex)),i=h(b.series.key,f,g,b,y);a.tooltip.show([d,e],i,undefined,undefined,c.offsetParent)};return m.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),m.dispatch.on("elementMouseout.tooltip",function(a){w.tooltipHide(a)}),n.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),n.dispatch.on("elementMouseout.tooltip",function(a){w.tooltipHide(a)}),o.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),o.dispatch.on("elementMouseout.tooltip",function(a){w.tooltipHide(a)}),p.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),p.dispatch.on("elementMouseout.tooltip",function(a){w.tooltipHide(a)}),q.dispatch.on("tooltipShow",function(a){if(!Math.round(q.y()(a.point)*100))return setTimeout(function(){d3.selectAll(".point.hover").classed("hover",!1)},0),!1;a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),q.dispatch.on("tooltipHide",function(a){w.tooltipHide(a)}),r.dispatch.on("tooltipShow",function(a){if(!Math.round(r.y()(a.point)*100))return setTimeout(function(){d3.selectAll(".point.hover").classed("hover",!1)},0),!1;a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),r.dispatch.on("tooltipHide",function(a){w.tooltipHide(a)}),m.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),m.dispatch.on("elementMouseout.tooltip",function(a){w.tooltipHide(a)}),n.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+b.left,a.pos[1]+b.top],w.tooltipShow(a)}),n.dispatch.on("elementMouseout.tooltip",function(a){w.tooltipHide(a)}),w.on("tooltipHide",function(){g&&a.tooltip.cleanup()}),y.dispatch=w,y.lines1=m,y.lines2=n,y.bars1=o,y.bars2=p,y.stack1=q,y.stack2=r,y.xAxis=s,y.yAxis1=t,y.yAxis2=u,y.x=function(a){return arguments.length?(getX=a,m.x(a),o.x(a),y):getX},y.y=function(a){return arguments.length?(getY=a,m.y(a),o.y(a),y):getY},y.margin=function(a){return arguments.length?(b=a,y):b},y.width=function(a){return arguments.length?(d=a,y):d},y.height=function(a){return arguments.length?(e=a,y):e},y.color=function(a){return arguments.length?(c=a,v.color(a),y):c},y.showLegend=function(a){return arguments.length?(f=a,y):f},y.tooltips=function(a){return arguments.length?(g=a,y):g},y.tooltipContent=function(a){return arguments.length?(h=a,y):h},y},a.models.ohlcBar=function(){function v(a){return a.each(function(a){var r=c-b.left-b.right,v=d-b.top-b.bottom,w=d3.select(this);f.domain(s||d3.extent(a[0].values.map(h).concat(n))),p?f.range([r*.5/a[0].values.length,r*(a[0].values.length-.5)/a[0].values.length]):f.range([0,r]),g.domain(t||[d3.min(a[0].values.map(m).concat(o)),d3.max(a[0].values.map(l).concat(o))]).range([v,0]);if(f.domain()[0]===f.domain()[1]||g.domain()[0]===g.domain()[1])singlePoint=!0;f.domain()[0]===f.domain()[1]&&(f.domain()[0]?f.domain([f.domain()[0]-f.domain()[0]*.01,f.domain()[1]+f.domain()[1]*.01]):f.domain([-1,1])),g.domain()[0]===g.domain()[1]&&(g.domain()[0]?g.domain([g.domain()[0]+g.domain()[0]*.01,g.domain()[1]-g.domain()[1]*.01]):g.domain([-1,1]));var z=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([a[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-ticks"),z.attr("transform","translate("+b.left+","+b.top+")"),w.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:e})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+e).append("rect"),z.select("#nv-chart-clip-path-"+e+" rect").attr("width",r).attr("height",v),D.attr("clip-path",q?"url(#nv-chart-clip-path-"+e+")":"");var E=z.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});E.exit().remove();var F=E.enter().append("path").attr("class",function(a,b,c){return(j(a,b)>k(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(b,c){var d=r/a[0].values.length*.9;return"m0,0l0,"+(g(j(b,c))-g(l(b,c)))+"l"+ -d/2+",0l"+d/2+",0l0,"+(g(m(b,c))-g(j(b,c)))+"l0,"+(g(k(b,c))-g(m(b,c)))+"l"+d/2+",0l"+ -d/2+",0z"}).attr("transform",function(a,b){return"translate("+f(h(a,b))+","+g(l(a,b))+")"}).on("mouseover",function(b,c){d3.select(this).classed("hover",!0),u.elementMouseover({point:b,series:a[0],pos:[f(h(b,c)),g(i(b,c))],pointIndex:c,seriesIndex:0,e:d3.event})}).on("mouseout",function(b,c){d3.select(this).classed("hover",!1),u.elementMouseout({point:b,series:a[0],pointIndex:c,seriesIndex:0,e:d3.event})}).on("click",function(a,b){u.elementClick({value:i(a,b),data:a,index:b,pos:[f(h(a,b)),g(i(a,b))],e:d3.event,id:e}),d3.event.stopPropagation()}).on("dblclick",function(a,b){u.elementDblClick({value:i(a,b),data:a,index:b,pos:[f(h(a,b)),g(i(a,b))],e:d3.event,id:e}),d3.event.stopPropagation()});E.attr("class",function(a,b,c){return(j(a,b)>k(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(E).attr("transform",function(a,b){return"translate("+f(h(a,b))+","+g(l(a,b))+")"}).attr("d",function(b,c){var d=r/a[0].values.length*.9;return"m0,0l0,"+(g(j(b,c))-g(l(b,c)))+"l"+ -d/2+",0l"+d/2+",0l0,"+(g(m(b,c))-g(j(b,c)))+"l0,"+(g(k(b,c))-g(m(b,c)))+"l"+d/2+",0l"+ -d/2+",0z"})}),v}var b={top:0,right:0,bottom:0,left +:0},c=960,d=500,e=Math.floor(Math.random()*1e4),f=d3.scale.linear(),g=d3.scale.linear(),h=function(a){return a.x},i=function(a){return a.y},j=function(a){return a.open},k=function(a){return a.close},l=function(a){return a.high},m=function(a){return a.low},n=[],o=[],p=!1,q=!0,r=a.utils.defaultColor(),s,t,u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return v.dispatch=u,v.x=function(a){return arguments.length?(h=a,v):h},v.y=function(a){return arguments.length?(i=a,v):i},v.open=function(a){return arguments.length?(j=a,v):j},v.close=function(a){return arguments.length?(k=a,v):k},v.high=function(a){return arguments.length?(l=a,v):l},v.low=function(a){return arguments.length?(m=a,v):m},v.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,v):b},v.width=function(a){return arguments.length?(c=a,v):c},v.height=function(a){return arguments.length?(d=a,v):d},v.xScale=function(a){return arguments.length?(f=a,v):f},v.yScale=function(a){return arguments.length?(g=a,v):g},v.xDomain=function(a){return arguments.length?(s=a,v):s},v.yDomain=function(a){return arguments.length?(t=a,v):t},v.forceX=function(a){return arguments.length?(n=a,v):n},v.forceY=function(a){return arguments.length?(o=a,v):o},v.padData=function(a){return arguments.length?(p=a,v):p},v.clipEdge=function(a){return arguments.length?(q=a,v):q},v.color=function(b){return arguments.length?(r=a.utils.getColor(b),v):r},v.id=function(a){return arguments.length?(e=a,v):e},v},a.models.pie=function(){function p(a){return a.each(function(a){function C(a){var b=(a.startAngle+a.endAngle)*90/Math.PI-90;return b>90?b-180:b}function D(a){n||(a.innerRadius=0);var b=d3.interpolate(this._current,a);return this._current=b(0),function(a){return w(b(a))}}function E(a){a.innerRadius=0;var b=d3.interpolate({startAngle:0,endAngle:0},a);return function(a){return w(b(a))}}var j=c-b.left-b.right,p=d-b.top-b.bottom,q=Math.min(j,p)/2,r=d3.select(this),s=r.selectAll(".nv-wrap.nv-pie").data([e(a[0])]),t=s.enter().append("g").attr("class","nvd3 nv-wrap nv-pie nv-chart-"+h),u=t.append("g"),v=s.select("g");u.append("g").attr("class","nv-pie"),s.attr("transform","translate("+b.left+","+b.top+")"),v.select(".nv-pie").attr("transform","translate("+j/2+","+p/2+")"),r.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:h})});var w=d3.svg.arc().outerRadius(q-q/5);n&&w.innerRadius(q/2);var x=d3.layout.pie().sort(null).value(function(a){return a.disabled?0:g(a)}),y=s.select(".nv-pie").selectAll(".nv-slice").data(x);y.exit().remove();var z=y.enter().append("g").attr("class","nv-slice").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:f(a.data),value:g(a.data),point:a.data,pointIndex:b,pos:[d3.event.pageX,d3.event.pageY],id:h})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:f(a.data),value:g(a.data),point:a.data,index:b,id:h})}).on("click",function(a,b){o.elementClick({label:f(a.data),value:g(a.data),point:a.data,index:b,pos:d3.event,id:h}),d3.event.stopPropagation()}).on("dblclick",function(a,b){o.elementDblClick({label:f(a.data),value:g(a.data),point:a.data,index:b,pos:d3.event,id:h}),d3.event.stopPropagation()});y.attr("fill",function(a,b){return i(a,b)}).attr("stroke",function(a,b){return i(a,b)});var A=z.append("path").each(function(a){this._current=a});d3.transition(y.select("path")).attr("d",w).attrTween("d",D);if(k){var B=w;l&&(B=d3.svg.arc().outerRadius(w.outerRadius())),z.append("g").classed("nv-label",!0).each(function(a,b){var c=d3.select(this);c.attr("transform",function(a){return a.outerRadius=q+10,a.innerRadius=q+15,"translate("+B.centroid(a)+")"}),c.append("rect").style("stroke","#fff").style("fill","#fff").attr("rx",3).attr("ry",3),c.append("text").style("text-anchor","middle").style("fill","#000")}),y.select(".nv-label").transition().attr("transform",function(a){return a.outerRadius=q+10,a.innerRadius=q+15,"translate("+B.centroid(a)+")"}),y.each(function(a,b){var c=d3.select(this);c.select(".nv-label text").text(function(a,b){var c=(a.endAngle-a.startAngle)/(2*Math.PI);return a.value&&c>m?f(a.data):""});var d=c.select("text").node().getBBox();c.select(".nv-label rect").attr("width",d.width+10).attr("height",d.height+10).attr("transform",function(){return"translate("+[d.x-5,d.y-5]+")"})})}}),p}var b={top:0,right:0,bottom:0,left:0},c=500,d=500,e=function(a){return a.values},f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(Math.random()*1e4),i=a.utils.defaultColor(),j=d3.format(",.2f"),k=!0,l=!1,m=.02,n=!1,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return p.dispatch=o,p.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,p):b},p.width=function(a){return arguments.length?(c=a,p):c},p.height=function(a){return arguments.length?(d=a,p):d},p.values=function(a){return arguments.length?(e=a,p):e},p.x=function(a){return arguments.length?(f=a,p):f},p.y=function(a){return arguments.length?(g=d3.functor(a),p):g},p.showLabels=function(a){return arguments.length?(k=a,p):k},p.donutLabelsOutside=function(a){return arguments.length?(l=a,p):l},p.donut=function(a){return arguments.length?(n=a,p):n},p.id=function(a){return arguments.length?(h=a,p):h},p.color=function(b){return arguments.length?(i=a.utils.getColor(b),p):i},p.valueFormat=function(a){return arguments.length?(j=a,p):j},p.labelThreshold=function(a){return arguments.length?(m=a,p):m},p},a.models.pieChart=function(){function o(a){return a.each(function(h){var i=d3.select(this),j=this,n=(e||parseInt(i.style("width"))||960)-d.left-d.right,p=(f||parseInt(i.style("height"))||400)-d.top-d.bottom;o.update=function(){o(a)},o.container=this;if(!h||!h.length){var q=i.selectAll(".nv-noData").data([l]);return q.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),q.attr("x",d.left+n/2).attr("y",d.top+p/2).text(function(a){return a}),o}i.selectAll(".nv-noData").remove();var r=i.selectAll("g.nv-wrap.nv-pieChart").data([h]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),t=r.select("g");s.append("g").attr("class","nv-pieWrap"),s.append("g").attr("class","nv-legendWrap"),g&&(c.width(n).key(b.x()),r.select(".nv-legendWrap").datum(b.values()(h[0])).call(c),d.top!=c.height()&&(d.top=c.height(),p=(f||parseInt(i.style("height"))||400)-d.top-d.bottom),r.select(".nv-legendWrap").attr("transform","translate(0,"+ -d.top+")")),r.attr("transform","translate("+d.left+","+d.top+")"),b.width(n).height(p);var u=t.select(".nv-pieWrap").datum(h);d3.transition(u).call(b),c.dispatch.on("legendClick",function(c,d,e){c.disabled=!c.disabled,b.values()(h[0]).filter(function(a){return!a.disabled}).length||b.values()(h[0]).map(function(a){return a.disabled=!1,r.selectAll(".nv-series").classed("disabled",!1),a}),k.disabled=h[0].map(function(a){return!!a.disabled}),m.stateChange(k),a.transition().call(o)}),b.dispatch.on("elementMouseout.tooltip",function(a){m.tooltipHide(a)}),m.on("changeState",function(b){typeof b.disabled!="undefined"&&(h[0].forEach(function(a,c){a.disabled=b.disabled[c]}),k.disabled=b.disabled),a.call(o)})}),o}var b=a.models.pie(),c=a.models.legend(),d={top:30,right:20,bottom:20,left:20},e=null,f=null,g=!0,h=a.utils.defaultColor(),i=!0,j=function(a,b,c,d){return"

"+a+"

"+"

"+b+"

"},k={},l="No Data Available.",m=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),n=function(c,d){var e=c.pos[0]+(d&&d.offsetLeft||0),f=c.pos[1]+(d&&d.offsetTop||0),g=b.valueFormat()(b.y()(c.point)),h=j(b.x()(c.point),g,c,o);a.tooltip.show([e,f],h,c.value<0?"n":"s",null,d)};return b.dispatch.on("elementMouseover.tooltip",function(a){a.pos=[a.pos[0]+d.left,a.pos[1]+d.top],m.tooltipShow(a)}),m.on("tooltipShow",function(a){i&&n(a)}),m.on("tooltipHide",function(){i&&a.tooltip.cleanup()}),o.legend=c,o.dispatch=m,o.pie=b,d3.rebind(o,b,"valueFormat","values","x","y","id","showLabels","donutLabelsOutside","donut","labelThreshold"),o.margin=function(a){return arguments.length?(d.top=typeof a.top!="undefined"?a.top:d.top,d.right=typeof a.right!="undefined"?a.right:d.right,d.bottom=typeof a.bottom!="undefined"?a.bottom:d.bottom,d.left=typeof a.left!="undefined"?a.left:d.left,o):d},o.width=function(a){return arguments.length?(e=a,o):e},o.height=function(a){return arguments.length?(f=a,o):f},o.color=function(d){return arguments.length?(h=a.utils.getColor(d),c.color(h),b.color(h),o):h},o.showLegend=function(a){return arguments.length?(g=a,o):g},o.tooltips=function(a){return arguments.length?(i=a,o):i},o.tooltipContent=function(a){return arguments.length?(j=a,o):j},o.state=function(a){return arguments.length?(k=a,o):k},o.noData=function(a){return arguments.length?(l=a,o):l},o},a.models.scatter=function(){function J(a){return a.each(function(a){function S(){if(!r)return!1;var e,i=d3.merge(a.map(function(a,b){return a.values.map(function(a,c){return[g(j(a,c))*(Math.random()/1e12+1),h(k(a,c))*(Math.random()/1e12+1),b,c,a]}).filter(function(a,b){return s(a[4],b)})}));if(D===!0){if(v){var l=N.select("defs").selectAll(".nv-point-clips").data([f]).enter();l.append("clipPath").attr("class","nv-point-clips").attr("id","nv-points-clip-"+f);var m=N.select("#nv-points-clip-"+f).selectAll("circle").data(i);m.enter().append("circle").attr("r",w),m.exit().remove(),m.attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}),N.select(".nv-point-paths").attr("clip-path","url(#nv-points-clip-"+f+")")}i.length<3&&(i.push([g.range()[0]-2e3,h.range()[0]-2e3,null,null]),i.push([g.range()[1]+2e3,h.range()[1]+2e3,null,null]),i.push([g.range()[0]-2e3,h.range()[0]+2e3,null,null]),i.push([g.range()[1]+2e3,h.range()[1]-2e3,null,null]));var n=d3.geom.polygon([[-10,-10],[-10,d+10],[c+10,d+10],[c+10,-10]]),o=d3.geom.voronoi(i).map(function(a,b){return{data:n.clip(a),series:i[b][2],point:i[b][3]}}),p=N.select(".nv-point-paths").selectAll("path").data(o);p.enter().append("path").attr("class",function(a,b){return"nv-path-"+b}),p.exit().remove(),p.attr("d",function(a){return"M"+a.data.join("L")+"Z"}),p.on("click",function(c){if(I)return 0;var d=a[c.series],e=d.values[c.point];C.elementClick({point:e,series:d,pos:[g(j(e,c.point))+b.left,h(k(e,c.point))+b.top],seriesIndex:c.series,pointIndex:c.point})}).on("mouseover",function(c){if(I)return 0;var d=a[c.series],e=d.values[c.point];C.elementMouseover({point:e,series:d,pos:[g(j(e,c.point))+b.left,h(k(e,c.point))+b.top],seriesIndex:c.series,pointIndex:c.point})}).on("mouseout",function(b,c){if(I)return 0;var d=a[b.series],e=d.values[b.point];C.elementMouseout({point:e,series:d,seriesIndex:b.series,pointIndex:b.point})})}else N.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(c,d){if(I)return 0;var e=a[c.series],f=e.values[d];C.elementClick({point:f,series:e,pos:[g(j(f,d))+b.left,h(k(f,d))+b.top],seriesIndex:c.series,pointIndex:d})}).on("mouseover",function(c,d){if(I)return 0;var e=a[c.series],f=e.values[d];C.elementMouseover({point:f,series:e,pos:[g(j(f,d))+b.left,h(k(f,d))+b.top],seriesIndex:c.series,pointIndex:d})}).on("mouseout",function(b,c){if(I)return 0;var d=a[b.series],e=d.values[c];C.elementMouseout({point:e,series:d,seriesIndex:b.series,pointIndex:c})});I=!1}var J=c-b.left-b.right,K=d-b.top-b.bottom,L=d3.select(this);a=a.map(function(a,b){return a.values=a.values.map(function(a){return a.series=b,a}),a});var M=x&&y&&z?[]:d3.merge(a.map(function(a){return a.values.map(function(a,b){return{x:j(a,b),y:k(a,b),size:l(a,b)}})}));g.domain(x||d3.extent(M.map(function(a){return a.x}).concat(o))),t?g.range([J*.5/a[0].values.length,J*(a[0].values.length-.5)/a[0].values.length]):g.range([0,J]),h.domain(y||d3.extent(M.map(function(a){return a.y}).concat(p))).range([K,0]),i.domain(z||d3.extent(M.map(function(a){return a.size}).concat(q))).range(A||[16,256]);if(g.domain()[0]===g.domain()[1]||h.domain()[0]===h.domain()[1])B=!0;g.domain()[0]===g.domain()[1]&&(g.domain()[0]?g.domain([g.domain()[0]-g.domain()[0]*.01,g.domain()[1]+g.domain()[1]*.01]):g.domain([-1,1])),h.domain()[0]===h.domain()[1]&&(h.domain()[0]?h.domain([h.domain()[0]+h.domain()[0]*.01,h.domain()[1]-h.domain()[1]*.01]):h.domain([-1,1])),E=E||g,F=F||h,G=G||i;var N=L.selectAll("g.nv-wrap.nv-scatter").data([a]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+f+(B?" nv-single-point":"")),P=O.append("defs"),Q=O.append("g"),R=N.select("g");Q.append("g").attr("class","nv-groups"),Q.append("g").attr("class","nv-point-paths"),N.attr("transform","translate("+b.left+","+b.top+")"),P.append("clipPath").attr("id","nv-edge-clip-"+f).append("rect"),N.select("#nv-edge-clip-"+f+" rect").attr("width",J).attr("height",K),R.attr("clip-path",u?"url(#nv-edge-clip-"+f+")":""),I=!0;var T=N.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});T.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(T.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),T.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),d3.transition(T).style("fill",function(a,b){return e(a,b)}).style("stroke",function(a,b){return e(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);if(n){var U=T.selectAll("circle.nv-point").data(function(a){return a.values});U.enter().append("circle").attr("cx",function(a,b){return E(j(a,b))}).attr("cy",function(a,b){return F(k(a,b))}).attr("r",function(a,b){return Math.sqrt(i(l(a,b))/Math.PI)}),U.exit().remove(),d3.transition(T.exit().selectAll("path.nv-point")).attr("cx",function(a,b){return g(j(a,b))}).attr("cy",function(a,b){return h(k(a,b))}).remove(),U.attr("class",function(a,b){return"nv-point nv-point-"+b}),d3.transition(U).attr("cx",function(a,b){return g(j(a,b))}).attr("cy",function(a,b){return h(k(a,b))}).attr("r",function(a,b){return Math.sqrt(i(l(a,b))/Math.PI)})}else{var U=T.selectAll("path.nv-point").data(function(a){return a.values});U.enter().append("path").attr("transform",function(a,b){return"translate("+E(j(a,b))+","+F(k(a,b))+")"}).attr("d",d3.svg.symbol().type(m).size(function(a,b){return i(l(a,b))})),U.exit().remove(),d3.transition(T.exit().selectAll("path.nv-point")).attr("transform",function(a,b){return"translate("+g(j(a,b))+","+h(k(a,b))+")"}).remove(),U.attr("class",function(a,b){return"nv-point nv-point-"+b}),d3.transition(U).attr("transform",function(a,b){return"translate("+g(j(a,b))+","+h(k(a,b))+")"}).attr("d",d3.svg.symbol().type(m).size(function(a,b){return i(l(a,b))}))}clearTimeout(H),H=setTimeout(S,300),E=g.copy(),F=h.copy(),G=i.copy()}),J}var b={top:0,right:0,bottom:0,left:0},c=960,d=500,e=a.utils.defaultColor(),f=Math.floor(Math.random()*1e5),g=d3.scale.linear(),h=d3.scale.linear(),i=d3.scale.linear(),j=function(a){return a.x},k=function(a){return a.y},l=function(a){return a.size||1},m=function(a){return a.shape||"circle"},n=!0,o=[],p=[],q=[],r=!0,s=function(a){return!a.notActive},t=!1,u=!1,v=!0,w=function(){return 25},x=null,y=null,z=null,A=null,B=!1,C=d3.dispatch("elementClick","elementMouseover","elementMouseout"),D=!0,E,F,G,H,I=!1;return C.on("elementMouseover.point",function(a){r&&d3.select(".nv-chart-"+f+" .nv-series-"+a.seriesIndex+" .nv-point-"+a.pointIndex).classed("hover",!0)}),C.on("elementMouseout.point",function(a){r&&d3.select(".nv-chart-"+f+" .nv-series-"+a.seriesIndex+" .nv-point-"+a.pointIndex).classed("hover",!1)}),J.dispatch=C,J.x=function(a){return arguments.length?(j=d3.functor(a),J):j},J.y=function(a){return arguments.length?(k=d3.functor(a),J):k},J.size=function(a){return arguments.length?(l=d3.functor(a),J):l},J.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,J):b},J.width=function(a){return arguments.length?(c=a,J):c},J.height=function(a){return arguments.length?(d=a,J):d},J.xScale=function(a){return arguments.length?(g=a,J):g},J.yScale=function(a){return arguments.length?(h=a,J):h},J.zScale=function(a){return arguments.length?(i=a,J):i},J.xDomain=function(a){return arguments.length?(x=a,J):x},J.yDomain=function(a){return arguments.length?(y=a,J):y},J.sizeDomain=function(a){return arguments.length?(z=a,J):z},J.sizeRange=function(a){return arguments.length?(A=a,J):A},J.forceX=function(a){return arguments.length?(o=a,J):o},J.forceY=function(a){return arguments.length?(p=a,J):p},J.forceSize=function(a){return arguments.length?(q=a,J):q},J.interactive=function(a){return arguments.length?(r=a,J):r},J.pointActive=function(a){return arguments.length?(s=a,J):s},J.padData=function(a){return arguments.length?(t=a,J):t},J.clipEdge=function(a){return arguments.length?(u=a,J):u},J.clipVoronoi=function(a){return arguments.length?(v=a,J):v},J.useVoronoi=function(a){return arguments.length?(D=a,D===!1&&(v=!1),J):D},J.clipRadius=function(a){return arguments.length?(w=a,J):w},J.color=function(b){return arguments.length?(e=a.utils.getColor(b),J):e},J.shape=function(a){return arguments.length?(m=a,J):m},J.onlyCircles=function(a){return arguments.length?(n=a,J):n},J.id=function(a){return arguments.length?(f=a,J):f},J.singlePoint=function(a){return arguments.length?(B=a,J):B},J},a.models.scatterChart=function(){function H(a){return a.each(function(x){function R(){if(v)return O.select(".nv-point-paths").style("pointer-events","all"),!1;O.select(".nv-point-paths").style("pointer-events","none");var a=d3.mouse(this);m.distortion(u).focus(a[0]),n.distortion(u).focus(a[1]),O.select(".nv-scatterWrap").call(b),O.select(".nv-x.nv-axis").call(c),O.select(".nv-y.nv-axis").call(d),O.select(".nv-distributionX").datum(x.filter(function(a){return!a.disabled})).call(g),O.select(".nv-distributionY").datum(x.filter(function(a){return!a.disabled})).call(h)}var y=d3.select(this),z=this,I=(j||parseInt(y.style("width"))||960)-i.left-i.right,J=(k||parseInt(y.style("height"))||400)-i.top-i.bottom;H.update=function(){H(a)},H.container=this;if(!x||!x.length||!x.filter(function(a){return a.values.length}).length){var K=y.selectAll(".nv-noData").data([B]);return K.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),K.attr("x",i.left+I/2).attr("y",i.top+J/2).text(function(a){return a}),H}y.selectAll(".nv-noData").remove(),D=D||m,E=E||n;var L=y.selectAll("g.nv-wrap.nv-scatterChart").data([x]),M=L.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+b.id()),N=M.append("g"),O=L.select("g");N.append("rect").attr("class","nvd3 nv-background"),N.append("g").attr("class","nv-x nv-axis"),N.append("g").attr("class","nv-y nv-axis"),N.append("g").attr("class","nv-scatterWrap"),N.append("g").attr("class","nv-distWrap"),N.append("g").attr("class","nv-legendWrap"),N.append("g").attr("class","nv-controlsWrap"),s&&(e.width(I/2),L.select(".nv-legendWrap").datum(x).call(e),i.top!=e.height()&&(i.top=e.height(),J=(k||parseInt(y.style("height"))||400)-i.top-i.bottom),L.select(".nv-legendWrap").attr("transform","translate("+I/2+","+ -i.top+")")),t&&(f.width(180).color(["#444"]),O.select(".nv-controlsWrap").datum(G).attr("transform","translate(0,"+ -i.top+")").call(f)),L.attr("transform","translate("+i.left+","+i.top+")"),b.width(I).height(J).color(x.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!x[b].disabled})),L.select(".nv-scatterWrap").datum(x.filter(function(a){return!a.disabled})).call(b);if(o){var P=m.domain()[1]-m.domain()[0];m.domain([m.domain()[0]-o*P,m.domain()[1]+o*P])}if(p){var Q=n.domain()[1]-n.domain()[0];n.domain([n.domain()[0]-p*Q,n.domain()[1]+p*Q])}c.scale(m).ticks(c.ticks()&&c.ticks().length?c.ticks():I/100).tickSize(-J,0),O.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")").call(c),d.scale(n).ticks(d.ticks()&&d.ticks().length?d.ticks():J/36).tickSize(-I,0),O.select(".nv-y.nv-axis").call(d),q&&(g.getData(b.x()).scale(m).width(I).color(x.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!x[b].disabled})),N.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),O.select(".nv-distributionX").attr("transform","translate(0,"+n.range()[0]+")").datum(x.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(b.y()).scale(n).width(J).color(x.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!x[b].disabled})),N.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),O.select(".nv-distributionY").attr("transform","translate(-"+h.size()+",0)").datum(x.filter(function(a){return!a.disabled})).call(h)),d3.fisheye&&(O.select(".nv-background").attr("width",I).attr("height",J),O.select(".nv-background").on("mousemove",R),O.select(".nv-background").on("click",function(){v=!v}),b.dispatch.on("elementClick.freezeFisheye",function(){v=!v})),f.dispatch.on("legendClick",function(e,f){e.disabled=!e.disabled,u=e.disabled?0:2.5,O.select(".nv-background").style("pointer-events",e.disabled?"none":"all"),O.select(".nv-point-paths").style("pointer-events",e.disabled?"all":"none"),e.disabled?(m.distortion(u).focus(0),n.distortion(u).focus(0),O.select(".nv-scatterWrap").call(b),O.select(".nv-x.nv-axis").call(c),O.select(".nv-y.nv-axis").call(d)):v=!1,H(a)}),e.dispatch.on("legendClick",function(b,c,d){b.disabled=!b.disabled,x.filter(function(a){return!a.disabled}).length||x.map(function(a){return a.disabled=!1,L.selectAll(".nv-series").classed("disabled",!1),a}),C.disabled=x.map(function(a){return!!a.disabled}),A.stateChange(C),H(a)}),b.dispatch.on("elementMouseover.tooltip",function(a){d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos[1]-J),d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos[0]+g.size()),a.pos=[a.pos[0]+i.left,a.pos[1]+i.top],A.tooltipShow(a)}),A.on("tooltipShow",function(a){w&&F(a,z.parentNode)}),A.on("changeState",function(b){typeof b.disabled!="undefined"&&(x.forEach(function(a,c){a.disabled=b.disabled[c]}),C.disabled=b.disabled),a.call(H)}),D=m.copy(),E=n.copy()}),H}var b=a.models.scatter(),c=a.models.axis(),d=a.models.axis(),e=a.models.legend(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i={top:30,right:20,bottom:50,left:75},j=null,k=null,l=a.utils.defaultColor(),m=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):b.xScale(),n=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):b.yScale(),o=0,p=0,q=!1,r=!1,s=!0,t=!!d3.fisheye,u=0,v=!1,w=!0,x=function(a,b,c){return""+b+""},y=function(a,b,c){return""+c+""},z=null,A=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),B="No Data Available.";b.xScale(m).yScale(n),c.orient("bottom").tickPadding(10),d.orient("left").tickPadding(10),g.axis("x"),h.axis("y");var C={},D,E,F=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),j=e.pos[0]+(f.offsetLeft||0),k=n.range()[0]+i.top+(f.offsetTop||0),l=m.range()[0]+i.left+(f.offsetLeft||0),o=e.pos[1]+(f.offsetTop||0),p=c.tickFormat()(b.x()(e.point,e.pointIndex)),q=d.tickFormat()(b.y()(e.point,e.pointIndex));x!=null&&a.tooltip.show([j,k],x(e.series.key,p,q,e,H),"n",1,f,"x-nvtooltip"),y!=null&&a.tooltip.show([l,o],y(e.series.key,p,q,e,H),"e",1,f,"y-nvtooltip"),z!=null&&a.tooltip.show([g,h],z(e.series.key,p,q,e,H),e.value<0?"n":"s",null,f)},G=[{key:"Magnify",disabled:!0}];return b.dispatch.on("elementMouseout.tooltip",function(a){A.tooltipHide(a),d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),A.on("tooltipHide",function(){w&&a.tooltip.cleanup()}),H.dispatch=A,H.scatter=b,H.legend=e,H.controls=f,H.xAxis=c,H.yAxis=d,H.distX=g,H.distY=h,d3.rebind(H,b,"id","interactive","pointActive","x","y","shape","size","xScale","yScale","zScale","xDomain","yDomain","sizeDomain","sizeRange","forceX","forceY","forceSize","clipVoronoi","clipRadius","useVoronoi"),H.margin=function(a){return arguments.length?(i.top=typeof a.top!="undefined"?a.top:i.top,i.right=typeof a.right!="undefined"?a.right:i.right,i.bottom=typeof a.bottom!="undefined"?a.bottom:i.bottom,i.left=typeof a.left!="undefined"?a.left:i.left,H):i},H.width=function(a){return arguments.length?(j=a,H):j},H.height=function(a){return arguments.length?(k=a,H):k},H.color=function(b){return arguments.length?(l=a.utils.getColor(b),e.color(l),g.color(l),h.color(l),H):l},H.showDistX=function(a){return arguments.length?(q=a,H):q},H.showDistY=function(a){return arguments.length?(r=a,H):r},H.showControls=function(a){return arguments.length?(t=a,H):t},H.showLegend=function(a){return arguments.length?(s=a,H):s},H.fisheye=function(a){return arguments.length?(u=a,H):u},H.xPadding=function(a){return arguments.length?(o=a,H):o},H.yPadding=function(a){return arguments.length?(p=a,H):p},H.tooltips=function(a){return arguments.length?(w=a,H):w},H.tooltipContent=function(a){return arguments.length?(z=a,H):z},H.tooltipXContent=function(a){return arguments.length?(x=a,H):x},H.tooltipYContent=function(a){return arguments.length?(y=a,H):y},H.state=function(a){return arguments.length?(C=a,H):C},H.noData=function(a){return arguments.length?(B=a,H):B},H},a.models.scatterPlusLineChart=function(){function F(a){return a.each(function(v){function P(){if(t)return M.select(".nv-point-paths").style("pointer-events","all"),!1;M.select(".nv-point-paths").style("pointer-events","none");var a=d3.mouse(this);m.distortion(s).focus(a[0]),n.distortion(s).focus(a[1]),M.select(".nv-scatterWrap").datum(v.filter(function(a){return!a.disabled})).call(b),M.select(".nv-x.nv-axis").call(c),M.select(".nv-y.nv-axis").call(d),M.select(".nv-distributionX").datum(v.filter(function(a){return!a.disabled})).call(g),M.select(".nv-distributionY").datum(v.filter(function(a){return!a.disabled})).call(h)}var w=d3.select(this),x=this,G=(j||parseInt(w.style("width"))||960)-i.left-i.right,H=(k||parseInt(w.style("height"))||400)-i.top-i.bottom;F.update=function(){F(a)},F.container=this;if(!v||!v.length||!v.filter(function(a){return a.values.length}).length){var I=w.selectAll(".nv-noData").data([z]);return I.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),I.attr("x",i.left+G/2).attr("y",i.top+H/2).text(function(a){return a}),F}w.selectAll(".nv-noData").remove(),m=b.xScale(),n=b.yScale(),B=B||m,C=C||n;var J=w.selectAll("g.nv-wrap.nv-scatterChart").data([v]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+b.id()),L=K.append("g"),M=J.select("g");L.append("rect").attr("class","nvd3 nv-background"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),L.append("g").attr("class","nv-controlsWrap"),J.attr("transform","translate("+i.left+","+i.top+")"),q&&(e.width(G/2),J.select(".nv-legendWrap").datum(v).call(e),i.top!=e.height()&&(i.top=e.height(),H=(k||parseInt(w.style("height"))||400)-i.top-i.bottom),J.select(".nv-legendWrap").attr("transform","translate("+G/2+","+ -i.top+")")),r&&(f.width(180).color(["#444"]),M.select(".nv-controlsWrap").datum(E).attr("transform","translate(0,"+ -i.top+")").call(f)),b.width(G).height(H).color(v.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!v[b].disabled})),J.select(".nv-scatterWrap").datum(v.filter(function(a){return!a.disabled})).call(b),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+b.id()+")");var N=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a}),O=N.enter().append("g").attr("class","nv-regLines").append("line").attr("class","nv-regLine").style("stroke-opacity",0);N.selectAll(".nv-regLines line").attr("x1",m.range()[0]).attr("x2",m.range()[1]).attr("y1",function(a,b){return n(m.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a,b){return n(m.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return l(a,c)}).style("stroke-opacity",function(a,b){return a.disabled||typeof a.slope=="undefined"||typeof a.intercept=="undefined"?0:1}),c.scale(m).ticks(c.ticks()?c.ticks():G/100).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")").call(c),d.scale(n).ticks(d.ticks()?d.ticks():H/36).tickSize(-G,0),M.select(".nv-y.nv-axis").call(d),o&&(g.getData(b.x()).scale(m).width(G).color(v.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!v[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+n.range()[0]+")").datum(v.filter(function(a){return!a.disabled})).call(g)),p&&(h.getData(b.y()).scale(n).width(H).color(v.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!v[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate(-"+h.size()+",0)").datum(v.filter(function(a){return!a.disabled})).call(h)),d3.fisheye&&(M.select(".nv-background").attr("width",G).attr("height",H),M.select(".nv-background").on("mousemove",P),M.select(".nv-background").on("click",function(){t=!t}),b.dispatch.on("elementClick.freezeFisheye",function(){t=!t})),f.dispatch.on("legendClick",function(e,f){e.disabled=!e.disabled,s=e.disabled?0:2.5,M.select(".nv-background").style("pointer-events",e.disabled?"none":"all"),M.select(".nv-point-paths").style("pointer-events",e.disabled?"all":"none"),e.disabled?(m.distortion(s).focus(0),n.distortion(s).focus(0),M.select(".nv-scatterWrap").call(b),M.select(".nv-x.nv-axis").call(c),M.select(".nv-y.nv-axis").call(d)):t=!1,F(a)}),e.dispatch.on("legendClick",function(b,c,d){b.disabled=!b.disabled,v.filter(function(a){return!a.disabled}).length||v.map(function(a){return a.disabled=!1,J.selectAll(".nv-series").classed("disabled",!1),a}),A.disabled=v.map(function(a){return!!a.disabled}),y.stateChange(A),F(a)}),b.dispatch.on("elementMouseover.tooltip",function(a){d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos[1]-H),d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos[0]+g.size()),a.pos=[a.pos[0]+i.left,a.pos[1]+i.top],y.tooltipShow(a)}),y.on("tooltipShow",function(a){u&&D(a,x.parentNode)}),y.on("changeState",function(b){typeof b.disabled!="undefined"&&(v.forEach(function(a,c){a.disabled=b.disabled[c]}),A.disabled=b.disabled),a.call(F)}),B=m.copy(),C=n.copy()}),F}var b=a.models.scatter(),c=a.models.axis(),d=a.models.axis(),e=a.models.legend(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i={top:30,right:20,bottom:50,left:75},j=null,k=null,l=a.utils.defaultColor(),m=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):b.xScale(),n=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):b.yScale(),o=!1,p=!1,q=!0,r=!!d3.fisheye,s=0,t=!1,u=!0,v=function(a,b,c){return""+b+""},w=function(a,b,c){return""+c+""},x=function(a,b,c,d){return"

"+a+"

"+"

"+d+"

"},y=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),z="No Data Available.";b.xScale(m).yScale(n),c.orient("bottom").tickPadding(10),d.orient("left").tickPadding(10),g.axis("x"),h.axis("y");var A={},B,C,D=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),j=e.pos[0]+(f.offsetLeft||0),k=n.range()[0]+i.top+(f.offsetTop||0),l=m.range()[0]+i.left+(f.offsetLeft||0),o=e.pos[1]+(f.offsetTop||0),p=c.tickFormat()(b.x()(e.point,e.pointIndex)),q=d.tickFormat()(b.y()(e.point,e.pointIndex));v!=null&&a.tooltip.show([j,k],v(e.series.key,p,q,e,F),"n",1,f,"x-nvtooltip"),w!=null&&a.tooltip.show([l,o],w(e.series.key,p,q,e,F),"e",1,f,"y-nvtooltip"),x!=null&&a.tooltip.show([g,h],x(e.series.key,p,q,e.point.tooltip,e,F),e.value<0?"n":"s",null,f)},E=[{key:"Magnify",disabled:!0}];return b.dispatch.on("elementMouseout.tooltip",function(a){y.tooltipHide(a),d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex +).attr("y1",0),d3.select(".nv-chart-"+b.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),y.on("tooltipHide",function(){u&&a.tooltip.cleanup()}),F.dispatch=y,F.scatter=b,F.legend=e,F.controls=f,F.xAxis=c,F.yAxis=d,F.distX=g,F.distY=h,d3.rebind(F,b,"id","interactive","pointActive","x","y","shape","size","xScale","yScale","zScale","xDomain","yDomain","sizeDomain","sizeRange","forceX","forceY","forceSize","clipVoronoi","clipRadius","useVoronoi"),F.margin=function(a){return arguments.length?(i.top=typeof a.top!="undefined"?a.top:i.top,i.right=typeof a.right!="undefined"?a.right:i.right,i.bottom=typeof a.bottom!="undefined"?a.bottom:i.bottom,i.left=typeof a.left!="undefined"?a.left:i.left,F):i},F.width=function(a){return arguments.length?(j=a,F):j},F.height=function(a){return arguments.length?(k=a,F):k},F.color=function(b){return arguments.length?(l=a.utils.getColor(b),e.color(l),g.color(l),h.color(l),F):l},F.showDistX=function(a){return arguments.length?(o=a,F):o},F.showDistY=function(a){return arguments.length?(p=a,F):p},F.showControls=function(a){return arguments.length?(r=a,F):r},F.showLegend=function(a){return arguments.length?(q=a,F):q},F.fisheye=function(a){return arguments.length?(s=a,F):s},F.tooltips=function(a){return arguments.length?(u=a,F):u},F.tooltipContent=function(a){return arguments.length?(x=a,F):x},F.tooltipXContent=function(a){return arguments.length?(v=a,F):v},F.tooltipYContent=function(a){return arguments.length?(w=a,F):w},F.state=function(a){return arguments.length?(A=a,F):A},F.noData=function(a){return arguments.length?(z=a,F):z},F},a.models.sparkline=function(){function m(a){return a.each(function(a){var e=c-b.left-b.right,m=d-b.top-b.bottom,n=d3.select(this);f.domain(k||d3.extent(a,h)).range([0,e]),g.domain(l||d3.extent(a,i)).range([m,0]);var o=n.selectAll("g.nv-wrap.nv-sparkline").data([a]),p=o.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline"),q=p.append("g"),r=o.select("g");o.attr("transform","translate("+b.left+","+b.top+")");var s=o.selectAll("path").data(function(a){return[a]});s.enter().append("path"),s.exit().remove(),s.style("stroke",function(a,b){return a.color||j(a,b)}).attr("d",d3.svg.line().x(function(a,b){return f(h(a,b))}).y(function(a,b){return g(i(a,b))}));var t=o.selectAll("circle.nv-point").data(function(a){function c(b){if(b!=-1){var c=a[b];return c.pointIndex=b,c}return null}var b=a.map(function(a,b){return i(a,b)}),d=c(b.lastIndexOf(g.domain()[1])),e=c(b.indexOf(g.domain()[0])),f=c(b.length-1);return[e,d,f].filter(function(a){return a!=null})});t.enter().append("circle"),t.exit().remove(),t.attr("cx",function(a,b){return f(h(a,a.pointIndex))}).attr("cy",function(a,b){return g(i(a,a.pointIndex))}).attr("r",2).attr("class",function(a,b){return h(a,a.pointIndex)==f.domain()[1]?"nv-point nv-currentValue":i(a,a.pointIndex)==g.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),m}var b={top:2,right:0,bottom:2,left:0},c=400,d=32,e=!0,f=d3.scale.linear(),g=d3.scale.linear(),h=function(a){return a.x},i=function(a){return a.y},j=a.utils.getColor(["#000"]),k,l;return m.margin=function(a){return arguments.length?(b.top=typeof a.top!="undefined"?a.top:b.top,b.right=typeof a.right!="undefined"?a.right:b.right,b.bottom=typeof a.bottom!="undefined"?a.bottom:b.bottom,b.left=typeof a.left!="undefined"?a.left:b.left,m):b},m.width=function(a){return arguments.length?(c=a,m):c},m.height=function(a){return arguments.length?(d=a,m):d},m.x=function(a){return arguments.length?(h=d3.functor(a),m):h},m.y=function(a){return arguments.length?(i=d3.functor(a),m):i},m.xScale=function(a){return arguments.length?(f=a,m):f},m.yScale=function(a){return arguments.length?(g=a,m):g},m.xDomain=function(a){return arguments.length?(k=a,m):k},m.yDomain=function(a){return arguments.length?(l=a,m):l},m.animate=function(a){return arguments.length?(e=a,m):e},m.color=function(b){return arguments.length?(j=a.utils.getColor(b),m):j},m},a.models.sparklinePlus=function(){function p(a){return a.each(function(l){function E(){if(i)return;var a=A.selectAll(".nv-hoverValue").data(h),d=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+f(b.x()(l[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1);if(!h.length)return;d.append("line").attr("x1",0).attr("y1",-c.top).attr("x2",0).attr("y2",s),d.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-c.top).attr("text-anchor","end").attr("dy",".9em"),A.select(".nv-hoverValue .nv-xValue").text(j(b.x()(l[h[0]],h[0]))),d.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-c.top).attr("text-anchor","start").attr("dy",".9em"),A.select(".nv-hoverValue .nv-yValue").text(k(b.y()(l[h[0]],h[0])))}function F(){function d(a,c){var d=Math.abs(b.x()(a[0],0)-c),e=0;for(var f=0;f"+a+""+"

"+c+" on "+b+"

"},o,p,q=d3.format(",.2f"),r={style:b.style()},s="No Data Available.",t=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");c.orient("bottom").tickPadding(7),d.orient("left"),b.scatter.pointActive(function(a){return!!Math.round(b.y()(a)*100)});var u=function(e,f){var g=e.pos[0]+(f.offsetLeft||0),h=e.pos[1]+(f.offsetTop||0),i=c.tickFormat()(b.x()(e.point,e.pointIndex)),j=d.tickFormat()(b.y()(e.point,e.pointIndex)),k=n(e.series.key,i,j,e,v);a.tooltip.show([g,h],k,e.value<0?"n":"s",null,f)};return b.dispatch.on("tooltipShow",function(a){a.pos=[a.pos[0]+g.left,a.pos[1]+g.top],t.tooltipShow(a)}),b.dispatch.on("tooltipHide",function(a){t.tooltipHide(a)}),t.on("tooltipHide",function(){m&&a.tooltip.cleanup()}),v.dispatch=t,v.stacked=b,v.legend=e,v.controls=f,v.xAxis=c,v.yAxis=d,d3.rebind(v,b,"x","y","size","xScale","yScale","xDomain","yDomain","sizeDomain","interactive","offset","order","style","clipEdge","forceX","forceY","forceSize","interpolate"),v.margin=function(a){return arguments.length?(g.top=typeof a.top!="undefined"?a.top:g.top,g.right=typeof a.right!="undefined"?a.right:g.right,g.bottom=typeof a.bottom!="undefined"?a.bottom:g.bottom,g.left=typeof a.left!="undefined"?a.left:g.left,v):g},v.width=function(a){return arguments.length?(h=a,v):getWidth},v.height=function(a){return arguments.length?(i=a,v):getHeight},v.color=function(c){return arguments.length?(j=a.utils.getColor(c),e.color(j),b.color(j),v):j},v.showControls=function(a){return arguments.length?(k=a,v):k},v.showLegend=function(a){return arguments.length?(l=a,v):l},v.tooltip=function(a){return arguments.length?(n=a,v):n},v.tooltips=function(a){return arguments.length?(m=a,v):m},v.tooltipContent=function(a){return arguments.length?(n=a,v):n},v.state=function(a){return arguments.length?(r=a,v):r},v.noData=function(a){return arguments.length?(s=a,v):s},d.setTickFormat=d.tickFormat,d.tickFormat=function(a){return arguments.length?(q=a,d):q},v}})(); \ No newline at end of file diff --git a/src/models/discreteBarChart.js b/src/models/discreteBarChart.js index 39eca0d..f5482e6 100644 --- a/src/models/discreteBarChart.js +++ b/src/models/discreteBarChart.js @@ -23,7 +23,7 @@ nv.models.discreteBarChart = function() { , x , y , noData = "No Data Available." - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate') ; xAxis @@ -68,7 +68,7 @@ nv.models.discreteBarChart = function() { - margin.top - margin.bottom; - chart.update = function() { selection.transition().call(chart); }; + chart.update = function() { dispatch.beforeUpdate(); selection.transition().call(chart); }; chart.container = this;