| |
| nv.models.stackedArea = function() { |
| //Default Settings |
| var margin = {top: 0, right: 0, bottom: 0, left: 0}, |
| width = 960, |
| height = 500, |
| color = d3.scale.category20().range(), // array of colors to be used in order |
| 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', |
| clipEdge = false; // if true, masks lines within x and y scale |
| |
| /************************************ |
| * offset: |
| * 'wiggle' (stream) |
| * 'zero' (stacked) |
| * 'expand' (normalize to 100%) |
| * 'silhouette' (simple centered) |
| * |
| * order: |
| * 'inside-out' (stream) |
| * 'default' (input order) |
| ************************************/ |
| |
| var scatter= nv.models.scatter() |
| .size(2.2) // default size |
| .sizeDomain([2.5]), //set to speed up calculation, needs to be unset if there is a cstom size accessor |
| x = scatter.xScale(), |
| y = scatter.yScale(), |
| dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout'); |
| |
| function chart(selection) { |
| selection.each(function(data) { |
| // Need to leave data alone to switch between stacked, stream, and expanded |
| var dataCopy = JSON.parse(JSON.stringify(data)), |
| availableWidth = width - margin.left - margin.right, |
| availableHeight = height - margin.top - margin.bottom; |
| |
| //TODO: try to get rid of the need for copying the data, and use the data directly |
| |
| //console.log(dataCopy); |
| dataCopy = dataCopy.map(function(series,i) { |
| if (series.disabled) |
| series.values = series.values.map(function(d,i) { |
| d._y = d.y || d._y; |
| d.y = 0; //TODO: need to use value from getY, not always d.y |
| return d |
| }); |
| return series; |
| }); |
| |
| |
| //TODO: deal with negative stacked charts |
| |
| //compute the data based on offset and order (calc's y0 for every point) |
| dataCopy = d3.layout.stack() |
| .offset(offset) |
| .order(order) |
| .values(function(d){ return d.values }) |
| .y(getY) |
| (dataCopy); |
| |
| |
| |
| var wrap = d3.select(this).selectAll('g.wrap.stackedarea').data([dataCopy]); |
| var wrapEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 stackedarea'); |
| var defsEnter = wrapEnter.append('defs'); |
| var gEnter = wrapEnter.append('g'); |
| var g = wrap.select('g'); |
| |
| gEnter.append('g').attr('class', 'areaWrap'); |
| |
| |
| scatter |
| .width(availableWidth) |
| .height(availableHeight) |
| //.x(getX) |
| .y(function(d) { return d.y + d.y0 }) // TODO: allow for getY to be other than d.y |
| .forceY([0]) |
| .color(dataCopy.map(function(d,i) { |
| return d.color || color[i % color.length]; |
| }).filter(function(d,i) { return !dataCopy[i].disabled })); |
| |
| gEnter.append('g').attr('class', 'scatterWrap'); |
| var scatterWrap= g.select('.scatterWrap') |
| .datum(dataCopy.filter(function(d) { return !d.disabled })) |
| |
| d3.transition(scatterWrap).call(scatter); |
| |
| |
| |
| |
| wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); |
| |
| |
| defsEnter.append('clipPath') |
| .attr('id', 'edge-clip-' + id) |
| .append('rect'); |
| |
| wrap.select('#edge-clip-' + id + ' rect') |
| .attr('width', availableWidth) |
| .attr('height', availableHeight); |
| |
| g .attr('clip-path', clipEdge ? 'url(#edge-clip-' + id + ')' : ''); |
| |
| |
| |
| |
| var area = d3.svg.area() |
| .x(function(d,i) { return x(scatter.x()(d,i)) }) |
| .y0(function(d) { return y(d.y0) }) |
| .y1(function(d) { return y(d.y + d.y0) }); |
| |
| var zeroArea = d3.svg.area() |
| .x(function(d,i) { return x(scatter.x()(d,i)) }) |
| .y0(function(d) { return y(d.y0) }) |
| .y1(function(d) { return y(d.y0) }); |
| |
| |
| var path = g.select('.areaWrap').selectAll('path.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 'area area-' + i }) |
| .on('mouseover', function(d,i) { |
| d3.select(this).classed('hover', true); |
| dispatch.areaMouseover({ |
| point: d, |
| series: d.key, |
| //pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], |
| pos: [d3.event.pageX, d3.event.pageY], |
| seriesIndex: i |
| //pointIndex: d.point |
| }); |
| }) |
| .on('mouseout', function(d,i) { |
| d3.select(this).classed('hover', false); |
| dispatch.areaMouseout({ |
| point: d, |
| series: d.key, |
| //pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], |
| pos: [d3.event.pageX, d3.event.pageY], |
| seriesIndex: i |
| //pointIndex: d.point |
| }); |
| }) |
| .on('click', function(d,i) { |
| d3.select(this).classed('hover', false); |
| dispatch.areaClick({ |
| point: d, |
| series: d.key, |
| //pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], |
| pos: [d3.event.pageX, d3.event.pageY], |
| seriesIndex: i |
| //pointIndex: d.point |
| }); |
| }) |
| d3.transition(path.exit()) |
| .attr('d', function(d,i) { return zeroArea(d.values,i) }) // TODO: fix this so transition is still fluid |
| .remove(); |
| path |
| .style('fill', function(d,i){ return d.color || color[i % color.length] }) |
| .style('stroke', function(d,i){ return d.color || color[i % color.length] }); |
| d3.transition(path) |
| .attr('d', function(d,i) { return area(d.values,i) }) |
| |
| |
| scatter.dispatch.on('elementClick.area', function(e) { |
| dispatch.areaClick(e); |
| }) |
| scatter.dispatch.on('elementMouseover.area', function(e) { |
| g.select('.area-' + e.seriesIndex).classed('hover', true); |
| }); |
| scatter.dispatch.on('elementMouseout.area', function(e) { |
| g.select('.area-' + e.seriesIndex).classed('hover', false); |
| }); |
| |
| }); |
| |
| |
| return chart; |
| } |
| |
| |
| chart.dispatch = dispatch; |
| chart.scatter = scatter; |
| |
| d3.rebind(chart, scatter, 'x', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); |
| |
| /* |
| chart.x = function(_) { |
| if (!arguments.length) return getX; |
| getX = d3.functor(_); |
| scatter.x(_); |
| return chart; |
| }; |
| */ |
| |
| chart.y = function(_) { |
| if (!arguments.length) return getY; |
| getY = d3.functor(_); |
| //scatter.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.clipEdge = function(_) { |
| if (!arguments.length) return clipEdge; |
| clipEdge = _; |
| return chart; |
| }; |
| |
| chart.color = function(_) { |
| if (!arguments.length) return color; |
| color = _; |
| 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': |
| offset = 'zero'; |
| order = 'default'; |
| break; |
| case 'stream': |
| offset = 'wiggle'; |
| order = 'inside-out'; |
| break; |
| case 'expand': |
| offset = 'expand'; |
| order = 'default'; |
| break; |
| } |
| |
| return chart; |
| }; |
| |
| |
| |
| 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); |
| }); |
| |
| |
| return chart; |
| } |