Попасть в линию очень тяжело. Подскажите сделать, что кликнув рядом с линией понять, что кликнули рядом и поставить точку на линию? Так же, если мы меняем масштаб, прилипание так же сработало.
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;
}
}
}
Вот алгоритм работающий через предварительный перебор точек с большИм шагом и последующий поиск в пространстве рядом с найденной точкой, с уменьшением шага.
Этот метод подойдет когда кривая, до которой необходимо искать кратчайшее расстояние, меняется со временем.
Производительность можно сильно повысить, если вешать обработчик не на все окно, а на толстую и полностью прозрачную копию линии, у которой установлена ширина, которая бы Вас устроила в качестве зоны прилипания.
—-
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)
<svg id="svg" width="600" height="175">
<path id="path" fill="none" stroke="red" d="M25,54C25,54,74,88,97,86C120,84,113,45,138,44C163,43,192,83,217,83C242,83,239,44,260,43C281,42,294,79,317,79C340,79,347,49,371,43C395,37,411,54,432,52C453,50,454,28,473,32C492,36,501,73,522,72C543,71,565,19,577,26C589,33,582,108,582,108" transform="scale(1)"></path>
<circle id="circle" r="5" fill="none" stroke="blue" cx="-10" ></circle>
<svg>
Можно найти расстояние от точки до прямой без итераций, через длину ортогональной проекции. Для этого достаточно немного доработать belongsToTheSide
//находим нормализованный вектор направления прямой
//если одна прямая используется много раз - можно вычислить единожды
dirx = line.x2 - line.x1;
diry = line.y2 - line.y1;
len = Math.sqrt(dirx * dirx + diry * diry);
dirx = dirx / len;
diry = diry / len;
//находим расстояние (знак говорит об ориентации, если расстояние больше нужного порога
dist = (x - line.x1) * diry - (y-line.y1) * dirx;
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Имеется Swiper sliderПосле клика по кнопке Next или свайпа необходимо добавить анимацию перед сменой слайда
Работаю с Vuejs, наткнулся скорей всего на магию callback-ов, как мне кажется