#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)
Комментариев нет:
Отправить комментарий