.ease() - Animation of pointer should be in an arched trajectory - animation

I'm playing around with ( https://gist.github.com/1499279 ) a custom gauge using d3js by Tomer. In his code, he calls updateGauges() gauges[key].redraw(30 + 50 * Math.random() - 30 * Math.random()); to generate random numbers for the pointer to animate to. I changed it to gauges[key].redraw(80); so that the pointer does not re-render.
During the initial load, the pointer follows a shortest path in getting to its destination instead of animating in an arched trajectory (clock wise like how real gauges operate). When 80 is passed in as parameter, the pointer animates anti clockwise from 0. Instead, it should always follow an arched trajectory.
What do you guys think should be modified/added?
*Soln Update:
this.drawPointer = function(value)
{
var delta = this.config.range / 13;
var n=value/2;
var head = this.valueToPoint(n, 0.85);
var head1 = this.valueToPoint(n - delta, 0.12);
var head2 = this.valueToPoint(n + delta, 0.12);
var headlast = this.valueToPoint(value, 0.85);
var head1last = this.valueToPoint(value - delta, 0.12);
var head2last = this.valueToPoint(value + delta, 0.12);
var tailValue = n - (this.config.range * (1/(270/360)) / 2);
var tail = this.valueToPoint(tailValue, 0.28);
var tail1 = this.valueToPoint(tailValue - delta, 0.12);
var tail2 = this.valueToPoint(tailValue + delta, 0.12);
var tailValuelast = value - (this.config.range * (1/(270/360)) / 2);
var taillast = this.valueToPoint(tailValuelast, 0.28);
var tail1last = this.valueToPoint(tailValuelast - delta, 0.12);
var tail2last = this.valueToPoint(tailValuelast + delta, 0.12);
var data = [head, head1, tail2, tail, tail1, head2, head];
var line = d3.svg.line()
.x(function(d) { return d.x })
.y(function(d) { return d.y })
.interpolate("basis");
var pointerContainer = this.body.select(".pointerContainer");
var pointer = pointerContainer.selectAll("path").data([data])
pointer.enter()
.append("svg:path")
.attr("d", line)
.style("fill", "#dc3912")
.style("stroke", "#c63310")
.style("fill-opacity", 0.7)
pointer.transition()
.attr("d", line)
.each("end", function () {
if (value!=0) {
var head = headlast;
var head1 = head1last;
var head2 = head2last;
var tailValue = tailValuelast;
var tail = taillast;
var tail1 = tail1last;
var tail2 = tail2last;
var data = [head, head1, tail2, tail, tail1, head2, head];
var line = d3.svg.line()
.x(function(d) { return d.x })
.y(function(d) { return d.y })
.interpolate("basis");
var pointer = pointerContainer.selectAll("path").data([data])
pointer.enter()
.append("svg:path")
.attr("d", line)
.style("fill", "#dc3912")
.style("stroke", "#c63310")
.style("fill-opacity", 0.7)
pointer.transition()
.attr("d", line)
.ease("bounce")
.duration(600);
}
}
)
.duration(200);
var fontSize = Math.round(this.config.size / 10);
pointerContainer.selectAll("text")
.data([value])
.text(Math.round(value))
.enter()
.append("svg:text")
.attr("x", this.config.cx)
.attr("y", this.config.size - this.config.cy / 4 - fontSize)
.attr("dy", fontSize / 2)
.attr("text-anchor", "middle")
.text(Math.round(value))
.style("font-size", fontSize + "px")
.style("fill", "#000")
.style("stroke-width", "0px");
}
Regards,
Viswesh

I am also new to D3, but here is my humble understanding (please correct me if I am wrong):
var line = d3.svg.line()
.x(function(d) { return d.x })
.y(function(d) { return d.y })
.interpolate("basis");
Here interpolate simply change x and y value from current value to target value.
In your case, the y value needs to change from current value -> highest point(which is 100) -> to the target value.
One of the solution i can think of is add control point.

Related

D3 Donut transition, d,i getting undefine

var arcMin = 75; // inner radius of the first arc
var arcWidth = 25; // width
var arcPad = 10; // padding between arcs
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
// console.log(d); <----getting undefine under attrTween Call
return 2*PI*d.value/100;
});
var path = g.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return d.data.color;
})
.transition()
.delay(function(d, i) {
return i * 800;
});
// .attrTween('d', function(d) {
// // This part make my chart disapear
// var i = d3.interpolate(d.startAngle, d.endAngle);
// return function(t) {
// d.endAngle = i(t);
// return arc(d);
// }
// // This part make my chart disapear
// });
arc(d) always return "M0,0Z"..
I found that the reason is when calling arc under arcTween, all d,i return undefine. How can i solve this.
Codes here: https://jsfiddle.net/m8oupfne/3/
Final product:
Couple things:
At first glance your attrTween function doesn't work because your arc function is dependent on both d,i and you only pass d to it.
But, fixing that doesn't make your chart transition nicely? Why? Because your arc function doesn't seem to make any sense. You use pie to calculate angles and then overwrite them in your arc function. And each call to the arc function calculates endAngle the same since it's based on d.value.
So, if you want a custom angle calculation, don't call pie at all, but pre-calculate your endAngle and don't do it in your arc function.
arc becomes:
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
});
Pre-calculate the data:
dataset.forEach(function(d,i){
d.endAngle = 2*PI*d.value/100;
d.startAngle = 0;
});
arcTween becomes:
.attrTween('d', function(d,i) {
var inter = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = inter(t);
return arc(d,i);
}
});
Running code:
(function(d3) {
'use strict';
var dataset = [
{ label: 'a', value: 88, color : '#898989'},
{ label: 'b', value: 56 , color : '#898989'},
{ label: 'c', value: 20 , color : '#FDD000'},
{ label: 'd', value: 46 , color : '#898989'},
];
var PI = Math.PI;
var arcMin = 75; // inner radius of the first arc
var arcWidth = 25; // width
var arcPad = 10; // padding between arcs
var arcBgColor = "#DCDDDD";
var width = 360;
var height = 360;
var radius = Math.min(width, height) / 2;
var donutWidth = 15; // NEW
var svg = d3.select('#canvas')
.append('svg')
.attr('width', width)
.attr('height', height);
var gBg = svg.append('g').attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var g = svg.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
});
var arcBg = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
return 2*PI;
});
var pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
var pathBg = gBg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arcBg)
.attr('fill', arcBgColor );
dataset.forEach(function(d,i){
d.endAngle = 2*PI*d.value/100;
d.startAngle = 0;
});
var path = g.selectAll('path')
.data(dataset)
.enter()
.append('path')
.attr('fill', function(d, i) {
return d.color;
})
.transition()
.duration(800)
.delay(function(d, i) {
return i * 800;
})
.attrTween('d', function(d,i) {
var inter = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = inter(t);
return arc(d,i);
}
});
})(window.d3);
<script src="https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/d3js/4.6.0/d3.min.js"></script>
<div id="canvas"></div>

How to change the size of dots in beeswarm plots in D3.js

I've been looking at this example of a beeswarm plot in d3.js and I'm trying to figure out how to change the size of the dots and without getting the circles to overlap. It seems if the radius of the dots change, it doesn't take this into account when running the calculations of where to place the dots.
This is a cool visualization.
I've made a plunk of it here: https://plnkr.co/edit/VwyXfbc94oXp6kXQ7JFx?p=preview and modified it to work a bit more like you're looking for (I think). The real key is changing the call to handle collision to vary based on the radius of the circles (in the original post it's hard coded to 4, which works well when r === 3 but fails as r grows). The changes:
Make the circle radius into a variable (line 7 of script.js, var r = 3;)
Change the d3.forceCollide call to use that radius and a multiplier - line 110 (.force("collide", d3.forceCollide(r * 1.333)))
Change the .enter() call to use that radius as well (line 130: .attr("r", r))
This works reasonably well for reasonable values of r - but you'll need to adjust the height, and it might even be nice to just change the whole thing so that r is based on height (e.g. var r = height * .01). You'll notice that as is now, the circles go off the bottom and top of the graph area.
This post might be of interest as well: Conflict between d3.forceCollide() and d3.forceX/Y() with high strength() value
Here's the whole of script.js for posterity:
var w = 1000, h = 280;
var padding = [0, 40, 34, 40];
var r = 5;
var xScale = d3.scaleLinear()
.range([ padding[3], w - padding[1] ]);
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
var colors = d3.scaleOrdinal()
.domain(["asia", "africa", "northAmerica", "europe", "southAmerica", "oceania"])
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33']);
d3.select("#africaColor").style("color", colors("africa"));
d3.select("#namericaColor").style("color", colors("northAmerica"));
d3.select("#samericaColor").style("color", colors("southAmerica"));
d3.select("#asiaColor").style("color", colors("asia"));
d3.select("#europeColor").style("color", colors("europe"));
d3.select("#oceaniaColor").style("color", colors("oceania"));
var formatNumber = d3.format(",");
var tt = d3.select("#svganchor").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#svganchor")
.append("svg")
.attr("width", w)
.attr("height", h);
var xline = svg.append("line")
.attr("stroke", "gray")
.attr("stroke-dasharray", "1,2");
var chartState = {};
chartState.variable = "totalEmission";
chartState.scale = "scaleLinear";
chartState.legend = "Total emissions, in kilotonnes";
d3.csv("co2bee.csv", function(error, data) {
if (error) throw error;
var dataSet = data;
xScale.domain(d3.extent(data, function(d) { return +d.totalEmission; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding[2]) + ")")
.call(xAxis);
var legend = svg.append("text")
.attr("text-anchor", "middle")
.attr("x", w / 2)
.attr("y", h - 4)
.attr("font-family", "PT Sans")
.attr("font-size", 12)
.attr("fill", "darkslategray")
.attr("fill-opacity", 1)
.attr("class", "legend");
redraw(chartState.variable);
d3.selectAll(".button1").on("click", function(){
var thisClicked = this.value;
chartState.variable = thisClicked;
if (thisClicked == "totalEmission"){
chartState.legend = "Total emissions, in kilotonnes";
}
if (thisClicked == "emissionPerCap"){
chartState.legend = "Per Capita emissions, in metric tons";
}
redraw(chartState.variable);
});
d3.selectAll(".button2").on("click", function(){
var thisClicked = this.value;
chartState.scale = thisClicked;
redraw(chartState.variable);
});
d3.selectAll("input").on("change", filter);
function redraw(variable){
if (chartState.scale == "scaleLinear"){ xScale = d3.scaleLinear().range([ padding[3], w - padding[1] ]);}
if (chartState.scale == "scaleLog"){ xScale = d3.scaleLog().range([ padding[3], w - padding[1] ]);}
xScale.domain(d3.extent(dataSet, function(d) { return +d[variable]; }));
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
d3.transition(svg).select(".x.axis").transition().duration(1000)
.call(xAxis);
var simulation = d3.forceSimulation(dataSet)
.force("x", d3.forceX(function(d) { return xScale(+d[variable]); }).strength(2))
.force("y", d3.forceY((h / 2)-padding[2]/2))
.force("collide", d3.forceCollide(r * 1.333))
.stop();
for (var i = 0; i < dataSet.length; ++i) simulation.tick();
var countriesCircles = svg.selectAll(".countries")
.data(dataSet, function(d) { return d.countryCode});
countriesCircles.exit()
.transition()
.duration(1000)
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.remove();
countriesCircles.enter()
.append("circle")
.attr("class", "countries")
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.attr("r", r)
.attr("fill", function(d){ return colors(d.continent)})
.merge(countriesCircles)
.transition()
.duration(2000)
.attr("cx", function(d) { console.log(d); return d.x; })
.attr("cy", function(d) { return d.y; });
legend.text(chartState.legend);
d3.selectAll(".countries").on("mousemove", function(d) {
tt.html("Country: <strong>" + d.countryName + "</strong><br>"
+ chartState.legend.slice(0, chartState.legend.indexOf(",")) + ": <strong>" + formatNumber(d[variable]) + "</strong>" + chartState.legend.slice(chartState.legend.lastIndexOf(" ")))
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
xline.attr("x1", d3.select(this).attr("cx"))
.attr("y1", d3.select(this).attr("cy"))
.attr("y2", (h - padding[2]))
.attr("x2", d3.select(this).attr("cx"))
.attr("opacity", 1);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
xline.attr("opacity", 0);
});
d3.selectAll(".x.axis, .legend").on("mousemove", function(){
tt.html("This axis uses SI prefixes:<br>m: 10<sup>-3</sup><br>k: 10<sup>3</sup><br>M: 10<sup>6</sup>")
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
});
//end of redraw
}
function filter(){
function getCheckedBoxes(chkboxName) {
var checkboxes = document.getElementsByName(chkboxName);
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].defaultValue);
}
}
return checkboxesChecked.length > 0 ? checkboxesChecked : null;
}
var checkedBoxes = getCheckedBoxes("continent");
var newData = [];
if (checkedBoxes == null){
dataSet = newData;
redraw();
return;
};
for (var i = 0; i < checkedBoxes.length; i++){
var newArray = data.filter(function(d){
return d.continent == checkedBoxes[i];
});
Array.prototype.push.apply(newData, newArray);
}
dataSet = newData;
redraw(chartState.variable);
//end of filter
}
//end of d3.csv
});

D3.js Radar chart line drawing

I am trying to create a radar chart similar to the link here (
http://www.larsko.org/v/euc/).
I was able to create axes (my work so far), but I am having a problem to draw lines in it.
For instance, if I have a list of values something like below, how can I draw a line in the radar chart?
var tempData = [56784, 5.898, 3417, 0, 0, 0]
Edit: I have included code. I am having a problem finding XY coordinates and I think XY value has to be derived from "scales".
var width = 1000,
height = 960,
r = (960 / 2) - 160;
var svg = d3.select("#radar")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + ", " + height / 2 + ")");
d3.csv("data/results.csv", function(data) {
var headerNames = d3.keys(data[0]);
headerNames.splice(0, 1); //Remove 'scenario'
var minList = $.map(headerNames, function(h) {
return d3.min($.map(data, function(d) {
return d[h];
}));
}),
maxList = $.map(headerNames, function(h) {
return d3.max($.map(data, function(d) {
return d[h];
}));
}),
scales = $.map(headerNames, function(h, i) {
return d3.scale.linear()
.domain([minList[i], maxList[i]])
.range([50, r]);
}),
axes = $.map(headerNames, function(h, i) {
return d3.svg.axis()
.scale(scales[i])
.tickSize(4);
});
function angle(i) {
return i * (2 * Math.PI / headerNames.length) + Math.PI / headerNames.length;
}
var line = d3.svg.line()
.interpolate("cardinal-closed")
/* computing X and Y: I am having a problem here
.x(function(d){ return scales(d); })
.y(function(d){ return scales(d); }); */
$.each(axes, function(i, a) {
svg.append("g")
.attr("transform", "rotate(" + Math.round(angle(i) * (180 / Math.PI)) + ")")
.call(a)
.selectAll("text")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "rotate(" + -angle(i) * (180 / Math.PI) + ")";
})
//Drawing line
svg.selectAll(".layer")
.data(data)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) {
return line(d);
})
}) // End CSV
Example results.csv
scenario,n_dead_oaks,percent_dead_oaks,infected_area_ha,money_spent,area_treated_ha,price_per_oak
baseline,56784,5.898,3417,0,0,0
scen2,52725,5.477,3294,382036,35,94.12071939
RS_1,58037,6.028,3407,796705,59,-635.8379888
RS_2,33571,3.487,2555,1841047,104,79.31103261
RS_3,46111,4.79,2762,1176461,61,110.227771
As Squeegy suggested, you should share some code showing your current progress and how you have achieved to create the axes.
Anyways, this is how I would go about this:
For a given list of values that you want to represent as a line, find the [x,y] coordinates of every point of the line, i.e. place your data-points on each axis. If you have a scale system in place already to draw your axes, this shouldn't be too hard.
Use d3.svg.line to draw a line that goes through all these points.
The code would end up looking like this:
var tempData = [56784, 5.898, 3417, 0, 0, 0];
/** compute tempPoints from tempData **/
var tempPoints = [[123, 30], [12, 123], [123, 123], [0,0], [0,0], [0,0]];
var line = d3.svg.line();
d3.select('svg').append('path').attr('d', line(tempPoints) + 'Z'); // the trailing Z closes the path
I think I have a solution for now and I appreciate all of your response! Here is my current solution for my posting.
function getRowValues(data) {
return $.map(data, function(d, i) {
if (i != "scenario") {
return d;
}
});
}
function getCoor(data) {
console.log(data);
var row = getRowValues(data),
x,
y,
coor = [];
for (var i = 0; i < row.length; i++) {
x = Math.round(Math.cos(angle(i)) * scales[i](row[i]));
y = Math.round(Math.sin(angle(i)) * scales[i](row[i]));
coor.push([x, y]);
}
return coor;
}
var line = d3.svg.line()
.interpolate("cardinal-closed")
.tension(0.85);
svg.selectAll(".layer")
.data(data)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) { return line(getCoor(d)) + "Z"; })
.style("stroke", function(d, i){ return colors[i]; })
.style("fill", "none");

Not able to add the fill between the paths. using d3js

i am trying to fill the background between 2 lines, but i am not getting any correct output.
and i would like to remove the tick line in the y axis as well. how to get this both?
here is my code : any one correct me please?
$(function(){
var m = [80, 80, 80, 80]; // margins
var w = 300; // width
var h = 450; // height
var plan = 55;
var actual = 38;
var variation = plan - actual;
var data = [0,plan];
var data1 = [0,actual];
var x = d3.scale.linear().domain([0, 2]).range([0, w]);
var y = d3.scale.linear().domain([0, 100]).range([h, 0]);
var line = d3.svg.line()
.x(function(d,i) {
return x(i);
})
.y(function(d) {
return y(d);
})
var graph = d3.select("#graph").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
var yAxisLeft = d3.svg.axis().scale(y).tickSize(-w).orient("left");
graph.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(-0,0)")
.call(yAxisLeft);
graph.append("svg:path").attr("d", line(data));
graph.append("svg:path").attr("d", line(data1));
//not able to fill the bg between 2 lines
var area = d3.svg.area()
.x(function(d, i) { return 0 })
.x1(function(d, i) { return plan })
.y0(function(d, i) { return y(actual); })
.y1(function(d, i) { return y(variation); })
.interpolate("basis");
graph.append("path")
.datum(data)
.attr("d", area)
.attr("fill", "#CCC");
});
Live Demo
Concerning your area :
var area = d3.svg.area()
.x(function(d, i) { return 0 })
.x1(function(d, i) { return plan })
.y0(function(d, i) { return y(actual); })
.y1(function(d, i) { return y(variation); })
.interpolate("basis");
x and x1 are returning static values, so it won't draw an area but just a line
your both lines have same x axis so you just have to specify .x()
y0 and y1 are also returnin static values
Here is a working version :
var area = d3.svg.area()
.x(function(d, i) { return x(i) })
.y0(function(d, i) { return y(data[i]); })
.y1(function(d, i) { return y(data1[i]); })
Also be careful, you have a fill: none; in your css file so you won't see anything.
Functional plunker : http://plnkr.co/edit/xFNF3BQzd0IO5bauAiFU?p=preview

Reference line for line graph

I'm trying to graph the median lifetime of our customers in D3.js. I have the data graphed out, but I can't figure out how to draw reference lines showing the median lifetime. I want vertical and horizontal reference lines that intersect my data at the 50% value of the y-axis.
Here's what I have currently:
The vertical reference line needs to intersect the data in the same place as the horizontal reference line.
Here's my code:
d3.json('data.json', function(billingData) {
var paying = billingData.paying;
var w = 800;
var h = 600;
var secondsInInterval = 604800000; // Seconds in a week
var padding = 50;
var age = function(beginDate, secondsInInterval) {
// Calculate how old a subscription is given it's begin date
var diff = new Date() - new Date(beginDate);
return Math.floor(diff / secondsInInterval);
}
var maxAge = d3.max(paying, function(d) { return age(d.subscription.activated_at, secondsInInterval); });
var breakdown = new Array(maxAge);
$.each(paying, function(i,d) {
d.age = age(d.subscription.activated_at, secondsInInterval);
for(var i = 0; i <= d.age; i++) {
if ( typeof breakdown[i] == 'undefined' ) breakdown[i] = 0;
breakdown[i]++;
}
});
// Scales
var xScale = d3.scale.linear().domain([0, maxAge]).range([padding,w-padding]);
var yScale = d3.scale.linear().domain([0, 1]).range([h-padding,padding]);
// Axes
var xAxis = d3.svg.axis().scale(xScale).tickSize(6,3).orient('bottom');
var yAxis = d3.svg.axis().scale(yScale).tickSize(6,3).tickFormat(d3.format('%')).orient('left');
var graph = d3.select('body').append('svg:svg')
.attr('width', 800)
.attr('height', 600);
var line = graph.selectAll('path.line')
.data([breakdown])
.enter()
.append('svg:path')
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('stroke-width', '1')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(i);
})
.y(function(d,i) {
return yScale(d/paying.length);
})
);
var xMedian = graph.selectAll('path.median.x')
.data([[[maxAge/2,0], [maxAge/2,1]]])
.enter()
.append('svg:path')
.attr('class', 'median x')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(d[0]);
})
.y(function(d,i) {
return yScale(d[1]);
})
);
var yMedian = graph.selectAll('path.median.y')
.data([[[0,.5], [maxAge,0.5]]])
.enter()
.append('svg:path')
.attr('class', 'median y')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(d[0]);
})
.y(function(d,i) {
return yScale(d[1]);
})
);
graph.append('g').attr('class', 'x-axis').call(xAxis).attr('transform', 'translate(0,' + (h - padding) + ')')
graph.append('g').attr('class', 'y-axis').call(yAxis).attr('transform', 'translate(' + padding + ',0)');
graph.append('text').attr('class', 'y-label').attr('text-anchor', 'middle').text('customers').attr('transform', 'translate(10,' + (h / 2) + '), rotate(-90)');
graph.append('text').attr('class', 'x-label').attr('text-anchor', 'middle').text('lifetime (weeks)').attr('transform', 'translate(' + (w/2) + ',' + (h - padding + 40) + ')');
});
You need to search the point where the customers are 50% in your line (around 7 weeks), that's it, search the index i where breakdown[i]/paying.length is near 0.5, save that index as indexMedianCustomers (for example) and modify your code in
var xMedian = graph.selectAll('path.median.x')
.data([[[indexMedianCustomers,0], [indexMedianCustomers,1]]])
.enter()
.append('svg:path')
.attr('class', 'median x')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(d[0]);
})
.y(function(d,i) {
return yScale(d[1]);
})
);

Resources