score:10

Accepted answer

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.

score:0

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)
}

score:20

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
}

Related Query

More Query from same tag