В d3, как получить данные интерполированной линии из строки SVG?
Я показываю линейную диаграмму с D3 с примерно следующим кодом (с учетом функций масштабирования x
, y
и массива float data
):
var line = d3.svg.line()
.interpolate("basis")
.x(function (d, i) { return x(i); })
.y(function (d) { return y(d); });
d3.select('.line').attr('d', line(data));
Теперь я хочу знать вертикальную высоту линии в заданной горизонтальной позиции пикселя. Массив data
имеет меньшие точки данных, чем пиксели, а отображаемая строка интерполирована, поэтому не следует прямо выводить высоту строки на заданный пиксель только из массива data
.
Любые подсказки?
Ответы
Ответ 1
Отредактировано 19 сентября 2012 года за каждый комментарий с большим благодарением nrabinowitz!
Вам нужно будет выполнить поиск данных, возвращаемых getPointAtLength
. (См. 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")));
Для меня это возвращает [5.000403881072998, 140.6229248046875].
Эта функция поиска findYatX
далека от эффективности (выполняется в O (n) времени), но иллюстрирует точку.
Ответ 2
Это решение намного эффективнее принятого ответа. Это время выполнения является логарифмическим (в то время как принятый ответ имеет линейную сложность).
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
}
Ответ 3
Я пробовал реализовать findYatXbisection (как это было предложено bumbu), и я не мог заставить его работать КАК ЕСТЬ.
Вместо изменения длины как функции length_end и length_start, я просто уменьшил длину на 50% (если x < point.x) или увеличен на 50% (если x > point.x), но всегда относительно начальная длина нуля. Я также включил revXscale/revYscale для преобразования пикселей в значения x/y, заданные моими функциями d3.scale.
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)
}