I display a line chart with D3 with roughly the following code (given the scale functions x
, y
and the float array data
):
I have tried implementing findYatXbisection (as nicely suggested by bumbu), and I could not get it to work AS IS.
Instead of modifying the length as a function of length_end and length_start, I just decreased the length by 50% (if x < point.x) or increased by 50% (if x> point.x) but always relative to start length of zero. I have also incorporated revXscale/revYscale to convert pixels to x/y values as set by my d3.scale functions.
function findYatX(x,path,error){
var length = apath.getTotalLength()
, point = path.getPointAtLength(length)
, bisection_iterations_max=50
, bisection_iterations = 0
error = error || 0.1
while (x < revXscale(point.x) -error || x> revXscale(point.x + error) {
point = path.getPointAtlength(length)
if (x < revXscale(point.x)) {
length = length/2
} else {
length = 3/2*length
}
if (bisection_iterations_max < ++ bisection_iterations) {
break;
}
}
return revYscale(point.y)
}
Edited 19-Sep-2012 per comments with many thanks to nrabinowitz!
You will need to do some sort of search of the data returned by getPointAtLength
. (See https://developer.mozilla.org/en-US/docs/DOM/SVGPathElement.)
// Line
var line = d3.svg.line()
.interpolate("basis")
.x(function (d) { return i; })
.y(function(d, i) { return 100*Math.sin(i) + 100; });
// Append the path to the DOM
d3.select("svg#chart") //or whatever your SVG container is
.append("svg:path")
.attr("d", line([0,10,20,30,40,50,60,70,80,90,100]))
.attr("id", "myline");
// Get the coordinates
function findYatX(x, linePath) {
function getXY(len) {
var point = linePath.getPointAtLength(len);
return [point.x, point.y];
}
var curlen = 0;
while (getXY(curlen)[0] < x) { curlen += 0.01; }
return getXY(curlen);
}
console.log(findYatX(5, document.getElementById("myline")));
For me this returns [5.000403881072998, 140.6229248046875].
This search function, findYatX
, is far from efficient (runs in O(n) time), but illustrates the point.
This solution is much more efficient than the accepted answer. It's execution time is logarithmic (while accepted answer has linear complexity).
var findYatXbyBisection = function(x, path, error){
var length_end = path.getTotalLength()
, length_start = 0
, point = path.getPointAtLength((length_end + length_start) / 2) // get the middle point
, bisection_iterations_max = 50
, bisection_iterations = 0
error = error || 0.01
while (x < point.x - error || x > point.x + error) {
// get the middle point
point = path.getPointAtLength((length_end + length_start) / 2)
if (x < point.x) {
length_end = (length_start + length_end)/2
} else {
length_start = (length_start + length_end)/2
}
// Increase iteration
if(bisection_iterations_max < ++ bisection_iterations)
break;
}
return point.y
}