vertical slider brushs
input rate of change - output values
we could also add sliders to the value chart and reverse the calculation to get growth rate...
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| body{ | |
| font-family: sans-serif; | |
| } | |
| svg{ | |
| border: solid 1px #eee; | |
| } | |
| .axis { | |
| font: 10px sans-serif; | |
| -webkit-user-select: none; | |
| -moz-user-select: none; | |
| user-select: none; | |
| } | |
| .axis .domain { | |
| fill: none; | |
| stroke: #000; | |
| shape-rendering:crispEdges; | |
| } | |
| .axis .halo { | |
| fill: none; | |
| stroke-width: 8px; | |
| } | |
| .slider .handle { | |
| fill: #fff; | |
| stroke: #000; | |
| stroke-opacity: .5; | |
| stroke-width: 1.25px; | |
| pointer-events: none; | |
| } | |
| #growth-line{ | |
| stroke:#333; | |
| fill:none; | |
| } | |
| #value-line{ | |
| stroke:#333; | |
| fill:none; | |
| } | |
| </style> | |
| <body> | |
| <h2>Set growth rate:</h2> | |
| <div id="input"></div> | |
| <h2>Value:</h2> | |
| <div id="output"></div> | |
| </body> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| var sliderCount = 0; | |
| function nextID(){ | |
| sliderCount ++; | |
| return 'slider-'+sliderCount; | |
| } | |
| var width = 750; | |
| var height = 350; | |
| var sliderHeight = 300; | |
| var initialValue = 45; | |
| var growthData = [ | |
| { | |
| label:'jan2001', | |
| growthPct:70}, | |
| { | |
| label:'jan2002', | |
| growthPct:50}, | |
| { | |
| label:'jan2003', | |
| growthPct:30}, | |
| { | |
| label:'jan2004', | |
| growthPct:10}, | |
| { | |
| label:'jan2005', | |
| growthPct:0}]; | |
| var startValue = 200; | |
| growthData = calculateValues(growthData, startValue); | |
| console.log(growthData); | |
| //basic SVG structure | |
| var marginTransform = 'translate(0,20)'; | |
| var inputSVG = d3.select('#input').append('svg').attr('width',width).attr('height',height); | |
| var growthChart = inputSVG.append('g').attr('id','growthChart').attr('transform', marginTransform); | |
| var sliders = inputSVG.append('g').attr('id','ui').attr('transform', marginTransform); | |
| var outputSVG = d3.select('#output').append('svg').attr('width',width).attr('height',height); | |
| var valueChart = outputSVG.append('g').attr('id','valueChart').attr('transform', marginTransform); | |
| var growthRateScale = d3.scale.linear() | |
| .domain([0,100]) | |
| .range([sliderHeight,0]) | |
| .clamp(true); | |
| var valueScale = d3.scale.linear() | |
| .domain([0, 2000]) | |
| .range([sliderHeight,0]); | |
| var valueAxis = d3.svg.axis() | |
| .scale(valueScale) | |
| .orient('left') | |
| .tickSize(-5) | |
| .tickPadding(12); | |
| valueChart.append('g').attr('class','y axis').attr('transform','translate(100,0)') | |
| .call(valueAxis); | |
| var xScale = d3.scale.linear() | |
| .domain([0,growthData.length]) | |
| .range([100,width]); | |
| var growthLine = d3.svg.line() | |
| .x(function(d,i) { return xScale(i); }) | |
| .y(function(d) { return growthRateScale(d.growthPct); }); | |
| var valueLine = d3.svg.line() | |
| .x(function(d,i) { return xScale(i); }) | |
| .y(function(d) { return valueScale(d.value); }); | |
| var lookup = {}; | |
| for(var i=0; i<growthData.length;i++){ | |
| lookup[growthData[i].label] = i; | |
| addVerticalSlider(sliders, {x:xScale(i), y:0}, growthRateScale, setGrowth, growthData[i].label, growthData[i].growthPct); | |
| } | |
| addGrowthChart(growthChart, growthData); | |
| addValueChart(valueChart, growthData); | |
| function setGrowth(id,value){ | |
| growthData[ lookup[id] ].growthPct = value; | |
| growthData = calculateValues(growthData,100); | |
| updateGrowthChart(); | |
| updateValueChart(); | |
| } | |
| function addValueChart(parent, data){ | |
| parent.append('path').datum(data).attr('id','value-line') | |
| .attr('d',valueLine); | |
| } | |
| function updateValueChart(){ | |
| valueChart.select('#value-line') | |
| .transition().duration(10).attr("d", valueLine); | |
| } | |
| function updateGrowthChart(){ | |
| growthChart.select('#growth-line') | |
| .transition().duration(10).attr("d", growthLine); | |
| } | |
| function addGrowthChart(parent, data){ | |
| parent.append('path').datum(data).attr('id','growth-line') | |
| .attr('d',growthLine); | |
| } | |
| function addVerticalSlider( parent, position, scale, callback, id, defaultValue ){ | |
| if(!id){ | |
| id = nextID(); | |
| } | |
| var sliderID = id; | |
| var sliderHitWidth = 100; | |
| function brushed(){ | |
| var value = sliderBrush.extent()[0]; | |
| if (d3.event.sourceEvent) { // not a programmatic event | |
| value = scale.invert(d3.mouse(this)[1]); | |
| sliderBrush.extent([value, value]); | |
| } | |
| callback(sliderID,value); | |
| handle.attr('cy',scale(value)); | |
| } | |
| var sliderBrush = d3.svg.brush() | |
| .y(scale) | |
| .extent([0,0]) | |
| .on('brush', brushed); | |
| var sliderAxis = d3.svg.axis() | |
| .scale(scale) | |
| .orient('left') | |
| .tickFormat(function(d){return d+'%'}) | |
| .tickSize(0) | |
| .tickPadding(12); | |
| var sliderRoot = sliders.append('g') | |
| .attr('class','vslider-container') | |
| .attr('id',sliderID) | |
| .attr('transform','translate(' + position.x + ',' + position.y + ')'); | |
| sliderRoot.append('g').attr('class','y axis') | |
| .call(sliderAxis) | |
| .select('.domain') | |
| .select(function(){ return this.parentNode.appendChild(this.cloneNode(true)) }) | |
| .attr('class','halo'); | |
| var slider = sliderRoot.append("g") | |
| .attr("class", "slider") | |
| .call(sliderBrush); | |
| slider.selectAll(".extent,.resize") | |
| .remove(); | |
| slider.select('.background') | |
| .attr('width', sliderHitWidth) | |
| .attr('x',-sliderHitWidth/2); | |
| var handle = slider.append('circle') | |
| .attr('class','handle') | |
| .attr('r',10); | |
| slider | |
| .call(sliderBrush.event) | |
| .transition() // gratuitous intro! | |
| .duration(750) | |
| .call(sliderBrush.extent([defaultValue, defaultValue])) | |
| .call(sliderBrush.event); | |
| }; | |
| function calculateValues(data, first){ | |
| if(!first){ | |
| first = 100; | |
| } | |
| if(!data[0].value && first){ | |
| data[0].value = first; | |
| } | |
| for (var i=1;i<data.length;i++){ | |
| data[i].value = data[i-1].value * ( data[i-1].growthPct/100 + 1 ); | |
| } | |
| return data; | |
| } | |
| d3.select(self.frameElement).style("height", "775px"); | |
| </script> |