#javascript #svg #d3js
Попасть в линию очень тяжело. Подскажите сделать, что кликнув рядом с линией понять,
что кликнули рядом и поставить точку на линию? Так же, если мы меняем масштаб, прилипание
так же сработало.
drawing() {
this.svg_margin.left = 50;
this.svg_margin.right = 50;
this.svg_margin.top = 50;
this.svg_margin.bottom = 50;
this.draw_width = this.svg_width - this.svg_margin.left - this.svg_margin.right;
this.draw_height = this.svg_height - this.svg_margin.top - this.svg_margin.bottom;
let svg = d3.select('#svg-container')
.append('svg')
.attr('width', this.svg_width)
.attr('height', this.svg_height)
.style('border', '3px dashed lightgrey')
.style('background-color', '#4caf5026');
const x_scale = d3.scaleLinear().domain([0, this.draw_width]).range([0, this.draw_width]);
const y_scale = d3.scaleLinear().domain([this.draw_height, 0]).range([0, this.draw_height]);
// Создаём рабочую область
const svg_work_space = svg.append('g')
.attr('class', 'svg-work-space')
.style('fill', 'transparent');
const line = d3.line()
.x((d) => x_scale(d.x))
.y((d) => y_scale(d.y));
let points = [];
let rect = svg_work_space.append('rect')
.attr("x", 100)
.attr("y",100)
.attr("width",100)
.attr("height",100)
.attr("fill",'none')
.style('stroke-width', 5)
.attr('stroke', 'green');
let c = svg_work_space
.append("circle")
.attr('cx', 100)
.attr('cy', 100)
.attr('fill', 'red')
.attr('r', 1);
svg.on('click', function() {
let new_point = d3.mouse(this);
let new_coordinate = {
x: Math.round( x_scale.invert(new_point[0])),
y: Math.round( y_scale.invert(new_point[1]))
};
let line ={
x1:200,
y1:100,
x2:200,
y2:200,
};
points.push(new_coordinate);
svg_work_space.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr('cx', (d) => x_scale(d.x))
.attr('cy', (d) => y_scale(d.y))
.attr('fill', 'red')
.attr('r', 5);
// Проверяем принадлежит ли точка стороне
belongsToTheSide(new_coordinate.x, new_coordinate.y, line);
});
function belongsToTheSide(x,y, line) {
let equation = (x - line.x1)*(line.y2 - line.y1)-(line.x2 - line.x1)*(y-line.y1);
console.log('equation:',equation);
if (equation === 0 ) {
console.log('Точка принадлежит прямой');
return true;
} else if ( equation > 0) {
console.log('Точка находится справа от прямой');
return false;
} else if ( equation < 0) {
console.log('Точка находится слева от прямой');
return false;
}
}
}
Ответы
Ответ 1
Вот алгоритм работающий через предварительный перебор точек с большИм шагом и последующий поиск в пространстве рядом с найденной точкой, с уменьшением шага. Этот метод подойдет когда кривая, до которой необходимо искать кратчайшее расстояние, меняется со временем. Производительность можно сильно повысить, если вешать обработчик не на все окно, а на толстую и полностью прозрачную копию линии, у которой установлена ширина, которая бы Вас устроила в качестве зоны прилипания. —- PS: вот в этом ответе используется другой метод, через разбиение Вороного, и там как раз реализовано добавление точки в линию. Используйте этот метод если кривая не меняется, или меняется не часто. // максимальная дистанция от мышки до линии при которой будет появляться точка let d = 20; // положение svg-шки на экране let r = svg.getBoundingClientRect(); // невидимая svg точка, при помощи нее будут происходить трансформации координат, // чтобы перевести координаты мыши из системы координат окна в систему координат // svg пути(учесть всю иерархию transform-ов), к которому магнитим линию let pt = svg.createSVGPoint(); // масштаб let z = 1; addEventListener('mousemove', e => { // задаем невидимой точке координаты мыши pt.x = e.clientX; pt.y = e.clientY; // переводим эту точку в систему координат path let p = pt.matrixTransform(path.getScreenCTM().inverse()); // применяем алгоритм поиска ближайшей точки p = closestPoint(path, [p.x, p.y]); // задаем координаты найденной точки невидимой точке pt.x = p[0]; pt.y = p[1]; // переводим обратно в экранные координаты let p2 = pt.matrixTransform(path.getScreenCTM()); circle.setAttribute('cx', p2.x-r.x) circle.setAttribute('cy', p2.y-r.y) circle.setAttribute('opacity', p.distance < d/z ? 1 : 0) // показываем точку только если расстояние до нее меньше порогового значения svg.style.cursor = p.distance < d/z ? 'pointer':'default' }) // поиск ближайшей точки, принадлежащей линии function closestPoint(pathNode, point) { let pathLen=pathNode.getTotalLength(), precis=8, best, bestLen, bestDist=Infinity; // перебираем точки вдоль пути с большИм шагом, находим ту, // до которой минимальное расстояние for (let scan, scanLen = 0, scanDist; scanLen <= pathLen; scanLen += precis) if ((scanDist = dist(scan = pathNode.getPointAtLength(scanLen))) < bestDist) best = scan, bestLen = scanLen, bestDist = scanDist; precis /= 2; // сканируем пространство вблизи найденной точки влево и вправо уменьшая шаг между // пробами, сохраняя на каждой итерации наилучший результат (кратчайшее расстояние) while (precis > 0.5) { let bef, aft, befLen, aftLen, befDist, aftDist; if ((befLen = bestLen - precis) >= 0 && (befDist = dist(bef = pathNode.getPointAtLength(befLen))) < bestDist) best = bef, bestLen = befLen, bestDist = befDist; else if ((aftLen = bestLen + precis) <= pathLen && (aftDist = dist(aft = pathNode.getPointAtLength(aftLen))) < bestDist) best = aft, bestLen = aftLen, bestDist = aftDist; else precis /= 2; } best = [best.x, best.y]; best.distance = Math.sqrt(bestDist); return best; // евклидово расстояние между точками function dist(p) { let dx = p.x - point[0], dy = p.y - point[1]; return dx * dx + dy * dy; } } addEventListener('wheel', e => { z *= 1+0.1*Math.sign(e.deltaY); path.setAttribute('transform', `scale(${z})`) circle.setAttribute('opacity', 0) }, false)
Комментариев нет:
Отправить комментарий