| |
| nv.models.stackedArea = function() { |
| "use strict"; |
| //============================================================ |
| // 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 |
| , 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') |
| ; |
| |
| scatter |
| .size(2.2) // default size |
| .sizeDomain([2.2,2.2]) // all the same size by default |
| ; |
| |
| /************************************ |
| * offset: |
| * 'wiggle' (stream) |
| * 'zero' (stacked) |
| * 'expand' (normalize to 100%) |
| * 'silhouette' (simple centered) |
| * |
| * order: |
| * 'inside-out' (stream) |
| * 'default' (input order) |
| ************************************/ |
| |
| //============================================================ |
| |
| |
| 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(); |
| |
| //------------------------------------------------------------ |
| |
| var dataRaw = data; |
| // Injecting point index into each point because d3.layout.stack().out does not give index |
| data.forEach(function(aseries, i) { |
| aseries.seriesIndex = i; |
| aseries.values = aseries.values.map(function(d, j) { |
| d.index = j; |
| d.seriesIndex = i; |
| return d; |
| }); |
| }); |
| |
| var dataFiltered = data.filter(function(series) { |
| return !series.disabled; |
| }); |
| |
| 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(getY) |
| .out(function(d, y0, y) { |
| var yHeight = (getY(d) === 0) ? 0 : y; |
| d.display = { |
| y: yHeight, |
| y0: y0 |
| }; |
| }) |
| (dataFiltered); |
| |
| |
| //------------------------------------------------------------ |
| // 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'); |
| |
| gEnter.append('g').attr('class', 'nv-areaWrap'); |
| gEnter.append('g').attr('class', 'nv-scatterWrap'); |
| |
| wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); |
| |
| //------------------------------------------------------------ |
| |
| |
| 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, d.seriesIndex); |
| })); |
| |
| |
| var scatterWrap = g.select('.nv-scatterWrap') |
| .datum(data); |
| |
| scatterWrap.call(scatter); |
| |
| 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 + ')' : ''); |
| |
| 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); |
| |
| 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) }); |
| |
| |
| var path = g.select('.nv-areaWrap').selectAll('path.nv-area') |
| .data(function(d) { return d }); |
| |
| path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) |
| .attr('d', function(d,i){ |
| return zeroArea(d.values, d.seriesIndex); |
| }) |
| .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: d.seriesIndex |
| }); |
| }) |
| .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: d.seriesIndex |
| }); |
| }) |
| .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: d.seriesIndex |
| }); |
| }) |
| |
| path.exit().remove(); |
| |
| path |
| .style('fill', function(d,i){ |
| return d.color || color(d, d.seriesIndex) |
| }) |
| .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); |
| path.transition() |
| .attr('d', function(d,i) { |
| return area(d.values,i) |
| }); |
| |
| |
| |
| //============================================================ |
| // 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); |
| }); |
| |
| //============================================================ |
| //Special offset functions |
| chart.d3_stackedOffset_stackPercent = function(stackData) { |
| var n = stackData.length, //How many series |
| m = stackData[0].length, //how many points per series |
| k = 1 / n, |
| i, |
| j, |
| o, |
| y0 = []; |
| |
| for (j = 0; j < m; ++j) { //Looping through all points |
| for (i = 0, o = 0; i < dataRaw.length; i++) //looping through series' |
| o += getY(dataRaw[i].values[j]) //total value of all points at a certian point in time. |
| |
| if (o) for (i = 0; i < n; i++) |
| stackData[i][j][1] /= o; |
| else |
| for (i = 0; i < n; i++) |
| stackData[i][j][1] = k; |
| } |
| for (j = 0; j < m; ++j) y0[j] = 0; |
| return y0; |
| }; |
| |
| }); |
| |
| |
| return chart; |
| } |
| |
| |
| //============================================================ |
| // 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); |
| }); |
| |
| //============================================================ |
| |
| //============================================================ |
| // Global getters and setters |
| //------------------------------------------------------------ |
| |
| chart.dispatch = dispatch; |
| chart.scatter = scatter; |
| |
| d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', |
| 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights'); |
| |
| chart.options = nv.utils.optionsFunc.bind(chart); |
| |
| 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; |
| 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.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.offset = function(_) { |
| if (!arguments.length) return offset; |
| offset = _; |
| return chart; |
| }; |
| |
| chart.order = function(_) { |
| if (!arguments.length) return order; |
| order = _; |
| return chart; |
| }; |
| |
| //shortcut for offset + order |
| chart.style = function(_) { |
| if (!arguments.length) return style; |
| style = _; |
| |
| 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; |
| case 'stack_percent': |
| chart.offset(chart.d3_stackedOffset_stackPercent); |
| chart.order('default'); |
| break; |
| } |
| |
| return chart; |
| }; |
| |
| chart.interpolate = function(_) { |
| if (!arguments.length) return interpolate; |
| interpolate = _; |
| return chart; |
| }; |
| //============================================================ |
| |
| |
| return chart; |
| } |