Почему после увеличения уменьшение зума не корректно пересчитаваются координаты. Если изначально не зумить, то точки масштабируются корректно, даже при зуме. Но если поиграться, то всё отваливается. Подскажите, как правильно трансформировать круги?
let z = 1;
const svg = d3.select('#svg-container')
.append('svg')
.attr('width', 500)
.attr('height', 500)
.style('border', '3px dashed lightgrey')
.style('background-color', '#4caf5026');
const x_scale = d3.scaleLinear().domain([0, 3500]).range([0, 400]);
const y_scale = d3.scaleLinear().domain([0, 3500]).range([400, 0]);
const x_axis = d3.axisBottom(x_scale);
const y_axis = d3.axisLeft(y_scale);
const svg_WS = svg.append('g')
.attr('class', 'svg-work-space')
.attr("transform", `translate(50, 50)`)
.style('fill', 'transparent');
const svg_DS = svg_WS.append('g')
.attr('class', 'svg-draw-space')
.style('fill', 'transparent');
const g_x_axis = svg.append("g")
.attr("class", "axis axis-x")
.attr("transform", `translate(50, 450)`)
.call(x_axis);
const g_y_axis = svg.append("g")
.attr("class", "axis axis-y")
.attr("transform", `translate(50, 50)`)
.call(y_axis);
let points = [[1000,1000],[1000,2000],[2000,2000], [2000,1000]].map(d => ({x: d[0], y: d[1]}));
const zoom = d3.zoom().on("zoom", zoomed);
const line = d3.line()
.curve(d3.curveLinearClosed)
.x(d => x_scale(d.x))
.y(d => y_scale(d.y));
const self = this;
const path = svg_DS.append('path')
.datum(points)
.attr('fill', 'deepskyblue')
.attr('stroke', 'red')
.attr('stroke-width', 2)
.attr('d', line );
let circle = svg_DS.selectAll('.hover-circle').data([null]);
let circle1 = svg_DS.selectAll('.shpros-circle').data([null]);
circle = circle
.enter()
.append('circle')
.attr('fill', 'yellow')
.attr('class', 'hover-circle')
.attr('stroke', 'red')
.attr('stroke-width', 1)
.attr('r', 10)
.attr('opacity', 0)
.attr('cx', 100)
.attr('cy', 100);
svg.call(zoom);
function zoomed(){
path.attr("transform", d3.event.transform);
path.attr('stroke-width', 2/d3.event.transform.k);
d3.selectAll('.shpros-circle')
.attr("transform", d3.event.transform)
.attr("stroke-width", 1/d3.event.transform.k)
.attr("r", 5/d3.event.transform.k);
g_x_axis.call(x_axis.scale(d3.event.transform.rescaleX(x_scale)));
g_y_axis.call(y_axis.scale(d3.event.transform.rescaleY(y_scale)));
circle.attr('opacity', 0);
z = d3.event.transform.k;
}
// Максимальная дистанция от мыши до линии на которой будет появляться точка (круг)
let distance_to_line = 20;
// Положение svg на экране
let position_svg = svg.node().getBoundingClientRect();
// Невидимая svg точка, при помощи нее будут происходить трансформации координат.
// Чтобы перевести координаты мыши из системы координат окна в систему координат
// svg пути(учесть всю иерархию transform-ов), к которому магнитим линию
let default_point = svg.node().createSVGPoint();
let coordinate;
svg.on('mousemove', () => {
coordinate = getCorrectCoordinate();
circle
.attr('cx', coordinate.x)
.attr('cy', coordinate.y)
.attr('opacity', coordinate.distance < distance_to_line/z ? 1 : 0);
svg.style('cursor', coordinate.distance < distance_to_line/z ? 'pointer':'default');
});
svg.on('click', () => {
coordinate = getCorrectCoordinate();
if (!+circle.attr('opacity')){
return;
}
circle1
.enter()
.append('circle')
.attr('fill', 'yellow')
.attr('class', 'shpros-circle')
.attr('stroke', 'red')
.attr('stroke-width', 1)
.attr('r', 5)
.attr('opacity', 1)
.attr('cx',coordinate.x)
.attr('cy',coordinate.y)
.enter();
});
function getCorrectCoordinate(){
// Translate 50px + 3px border
const TRANSLATE = 53;
default_point.x = d3.event.clientX,
default_point.y = d3.event.clientY;
// Переводим эту точку в систему координат path
let path_point = default_point.matrixTransform(path.node().getScreenCTM().inverse());
// Применяем алгоритм(бинарный поиск) поиска ближайшей точки
path_point = closestPoint(path, [path_point.x, path_point.y]);
// Задаем координаты найденной точки невидимой точке
default_point.x = path_point[0];
default_point.y = path_point[1];
// Переводим обратно в экранные координаты
let correct_coordinate = default_point.matrixTransform(path.node().getScreenCTM());
// Поправка для масштабирования
correct_coordinate.x = Math.round(correct_coordinate.x - position_svg.x - TRANSLATE);
correct_coordinate.y = Math.round(correct_coordinate.y - position_svg.y - TRANSLATE);
correct_coordinate.distance = path_point.distance;
console.log(correct_coordinate);
return correct_coordinate;
}
function closestPoint(pathNode, point) {
let pathLen=pathNode.node().getTotalLength(), precis=8, best, bestLen, bestDist=Infinity;
for (let scan, scanLen = 0, scanDist; scanLen <= pathLen; scanLen += precis)
if ((scanDist = dist(scan = pathNode.node().getPointAtLength(scanLen))) < bestDist)
best = scan, bestLen = scanLen, bestDist = scanDist;
precis /= 2;
while (precis > 0.5/z) {
let bef, aft, befLen, aftLen, befDist, aftDist;
if ((befLen = bestLen - precis) >= 0 &&
(befDist = dist(bef = pathNode.node().getPointAtLength(befLen))) < bestDist)
best = bef, bestLen = befLen, bestDist = befDist;
else if ((aftLen = bestLen + precis) <= pathLen &&
(aftDist = dist(aft = pathNode.node().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;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="svg-container"></div>
Проблема в том, что когда Вы добавляете точку, её координата посчитана уже с учетом трансформации всего слоя, необходимо это учитывать, я пометил строки которые менял-добавлял
let t = {x:0,y:0,k:1};
const svg = d3.select('#svg-container')
.append('svg')
.attr('width', 500)
.attr('height', 500)
.style('border', '3px dashed lightgrey')
.style('background-color', '#4caf5026');
const x_scale = d3.scaleLinear().domain([0, 3500]).range([0, 400]);
const y_scale = d3.scaleLinear().domain([0, 3500]).range([400, 0]);
const x_axis = d3.axisBottom(x_scale);
const y_axis = d3.axisLeft(y_scale);
const svg_WS = svg.append('g')
.attr('class', 'svg-work-space')
.attr("transform", `translate(50, 50)`)
.style('fill', 'transparent');
const svg_DS = svg_WS.append('g')
.attr('class', 'svg-draw-space')
.style('fill', 'transparent');
const g_x_axis = svg.append("g")
.attr("class", "axis axis-x")
.attr("transform", `translate(50, 450)`)
.call(x_axis);
const g_y_axis = svg.append("g")
.attr("class", "axis axis-y")
.attr("transform", `translate(50, 50)`)
.call(y_axis);
let points = [[1000,1000],[1000,2000],[2000,2000], [2000,1000]].map(d => ({x: d[0], y: d[1]}));
const zoom = d3.zoom().on("zoom", zoomed);
const line = d3.line()
.curve(d3.curveLinearClosed)
.x(d => x_scale(d.x))
.y(d => y_scale(d.y));
const self = this;
const path = svg_DS.append('path')
.datum(points)
.attr('fill', 'deepskyblue')
.attr('stroke', 'red')
.attr('stroke-width', 2)
.attr('d', line );
let circle = svg_DS.selectAll('.hover-circle').data([null]);
let circle1 = svg_DS.selectAll('.shpros-circle').data([null]);
circle = circle
.enter()
.append('circle')
.attr('fill', 'yellow')
.attr('class', 'hover-circle')
.attr('stroke', 'red')
.attr('stroke-width', 1)
.attr('r', 10)
.attr('opacity', 0)
.attr('cx', 100)
.attr('cy', 100);
svg.call(zoom);
function zoomed(){
path.attr("transform", d3.event.transform);
path.attr('stroke-width', 2/d3.event.transform.k);
d3.selectAll('.shpros-circle')
.attr("transform", d3.event.transform)
.attr("stroke-width", 1/d3.event.transform.k)
.attr("r", 5/d3.event.transform.k);
g_x_axis.call(x_axis.scale(d3.event.transform.rescaleX(x_scale)));
g_y_axis.call(y_axis.scale(d3.event.transform.rescaleY(y_scale)));
circle.attr('opacity', 0);
t = d3.event.transform;
}
// Максимальная дистанция от мыши до линии на которой будет появляться точка (круг)
let distance_to_line = 20;
// Положение svg на экране
let position_svg = svg.node().getBoundingClientRect();
// Невидимая svg точка, при помощи нее будут происходить трансформации координат.
// Чтобы перевести координаты мыши из системы координат окна в систему координат
// svg пути(учесть всю иерархию transform-ов), к которому магнитим линию
let default_point = svg.node().createSVGPoint();
let coordinate;
svg.on('mousemove', () => {
coordinate = getCorrectCoordinate();
circle
.attr("r", 10/t.k) /// <<added
.attr('cx', coordinate.x)
.attr('cy', coordinate.y)
.attr('stroke-width', 2/t.k) /// <<added
.attr('transform',`translate(${t.x},${t.y}) scale(${t.k})`) /// <<added
.attr('opacity', coordinate.distance < distance_to_line/t.k ? 1 : 0);
svg.style('cursor', coordinate.distance < distance_to_line/t.k ? 'pointer':'default');
});
svg.on('click', () => {
coordinate = getCorrectCoordinate();
if (!+circle.attr('opacity')){
return;
}
circle1
.enter()
.append('circle')
.attr('fill', 'yellow')
.attr('class', 'shpros-circle')
.attr('stroke', 'red')
.attr('stroke-width', 1/t.k) /// <<added
.attr("r", 5/t.k) /// <<added
.attr('opacity', 1)
.attr('cx',coordinate.x)
.attr('cy',coordinate.y)
.attr('transform',`translate(${t.x},${t.y}) scale(${t.k})`) /// <<added
.enter();
});
function getCorrectCoordinate(){
// Translate 50px + 3px border
const TRANSLATE = 53;
default_point.x = d3.event.clientX,
default_point.y = d3.event.clientY;
// Переводим эту точку в систему координат path
let path_point = default_point.matrixTransform(path.node().getScreenCTM().inverse());
// Применяем алгоритм(бинарный поиск) поиска ближайшей точки
path_point = closestPoint(path, [path_point.x, path_point.y]);
// Задаем координаты найденной точки невидимой точке
default_point.x = path_point[0];
default_point.y = path_point[1];
// Переводим обратно в экранные координаты
let correct_coordinate = default_point.matrixTransform(svg_DS.node().getScreenCTM());
// ^^^^^ слой в координаты которого нужно сделать пересчет
// Поправка для масштабирования
correct_coordinate.x = correct_coordinate.x - position_svg.x - TRANSLATE;
correct_coordinate.y = correct_coordinate.y - position_svg.y - TRANSLATE;
correct_coordinate.distance = path_point.distance;
//console.log(correct_coordinate);
return correct_coordinate;
}
function closestPoint(pathNode, point) {
let pathLen=pathNode.node().getTotalLength(), precis=8, best, bestLen, bestDist=Infinity;
for (let scan, scanLen = 0, scanDist; scanLen <= pathLen; scanLen += precis)
if ((scanDist = dist(scan = pathNode.node().getPointAtLength(scanLen))) < bestDist)
best = scan, bestLen = scanLen, bestDist = scanDist;
precis /= 2;
while (precis > 0.5/t.k) {
let bef, aft, befLen, aftLen, befDist, aftDist;
if ((befLen = bestLen - precis) >= 0 &&
(befDist = dist(bef = pathNode.node().getPointAtLength(befLen))) < bestDist)
best = bef, bestLen = befLen, bestDist = befDist;
else if ((aftLen = bestLen + precis) <= pathLen &&
(aftDist = dist(aft = pathNode.node().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;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="svg-container"></div>
и да, d3 тут не при делах.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
При выборе поисковых подсказок в input не срабатывает функция oninput, onchange
Ситуация следующая: при тесте сайта на iPhone 6, после загрузки всей страницы фон становится белым, а должен быть градиентПроисходит это через...
Делаю на форуме в комментариях "якорь"Каждому комментарию присваивается уникальный ID
Чтобы увеличить резкость изображения я использую следующую функцию: