У меня есть <svg> с фоновыми изображениями и другим прямоугольником на этом svg, которые находятся в правильном положении.
Мои прямоугольники сгруппированы по <g> со значениями перевода, и каждый прямоугольник имеет определенные значения поворота. Теперь я хочу, чтобы мой SVG фокусировался (показывался в центре страницы) на определенном прямоугольнике, не нарушая фоновое изображение, и svg переводил туда. Основная проблема заключается в том, что эти прямоугольники тоже имеют разные углы.
Так что перевод не работает должным образом....
SVG выглядит так, как показано ниже.
Может ли кто-нибудь дать мне рекомендации?
<svg width = "4500" height = "3610" style = "background: url("/images/001.jpg") no-repeat;">
<g id = "228" transform = "translate(2001,620)rotate(216) translate(0 -216)">
<g>
<rect x = "0" y = "0" height = "72" width = "36" stroke = "black" style = "fill: transparent;">
</rect><text x = "0" y = "36" text-anchor = "start" style = "fill: black;">228</text>
</g>
<g>
<rect x = "36" y = "0" height = "72" width = "36" stroke = "black" style = "fill: transparent;">
</rect><text x = "36" y = "36" text-anchor = "start" style = "fill: black;">229</text>
</g>
</g>
</svg>



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это должно быть проще в SVG, и может быть, но вот мое решение:
Во-первых, нам нужно получить ограничивающую рамку прямоугольника, к которому мы хотим приблизиться, в его координатном пространстве:
// Bounds in local coordinates space:
var bounds = rectElement.getBBox();
Во-вторых, мы получаем четыре угла из этой ограничивающей рамки:
// Corners of rect in local coordinate space:
var corners = [
{x: bounds.x,y: bounds.y},
{x: bounds.x + bounds.width, y: bounds.y},
{x: bounds.x + bounds.width, y: bounds.y + bounds.height },
{x: bounds.x, y: bounds.y + bounds.height }
];
В-третьих, мы получаем матрицу преобразования между прямоугольником и SVG:
// relevant transform:
var t = rectElement.getCTM();
Теперь мы можем преобразовать углы, которые у нас есть, в глобальные единицы SVG:
// Convert the points to global SVG coordainte space:
for(var i = 0; i < corners.length; i++) {
var point = svg.node().createSVGPoint();
point.x = corners[i].x;
point.y = corners[i].y;
corners[i] = point.matrixTransform(t);
}
Хорошо, на данный момент у нас есть углы повернутого прямоугольника в пригодных для использования координатах. Мы хотим найти ширину и высоту этих координат:
// get extents for x,y in global SVG space:
var x = d3.extent(corners, function(d) { return d.x; });
var y = d3.extent(corners, function(d) { return d.y; });
var w = x[1] - x[0];
var h = y[1] - y[0];
Это дает нам повернутую ширину и высоту, которые мы теперь можем использовать для масштабирования SVG:. Здесь я использую небольшой запас в моей функции масштабирования (10% запаса с каждой стороны):
// For getting scale factor:
var scale = function(elementWidth,elementHeight,SVGWidth,SVGHeight) {
return Math.min(SVGWidth/elementWidth*0.8,SVGHeight/elementHeight*0.8);
}
Который я использую так:
var k = scale(w,h,SVGwidth,SVGheight);
В этот момент я могу приблизить прямоугольник с переводом -x[0]*k, -y[0]*k и масштабом k. Однако это приведет к форме в верхнем левом углу SVG, я хочу учесть поля и все, что ширина/высота прямоугольника меньше относительно ширины/высоты SVG. Для этого я вычисляю смещение для каждого из x, y:
// Offset to center feature:
var ox = (SVGwidth - w*k)/2;
var oy = (SVGheight- h*k)/2;
И теперь я могу создать свое преобразование для центрирования заданного прямоугольника в SVG:
var newTransform = "translate("+[-x[0]*k+ox,-y[0]*k+oy]+")scale("+k+")";
Эти фрагменты используют родителя g для хранения всего в SVG, что упрощает управление преобразованиями. Кроме того, у этого g его преобразование стирается при масштабировании до прямоугольника, потому что нам нужно не относительное преобразование из текущего состояния/преобразования масштабирования, а одно в исходное состояние/преобразование масштабирования, иначе мы увидим странное поведение.
Вот пример с поведением масштабирования, позволяющим масштабировать/панорамировать вручную, но с возможностью щелчка для масштабирования до прямоугольника:
var svg = d3.select("svg");
var contents = svg.html();
svg.selectAll("*").remove();
var g = svg.append("g")
.attr("transform", "translate(0,0)scale(1)")
.html(contents);
var SVGwidth = 400;
var SVGheight = 180;
// For getting scale factor:
var scale = function(elementWidth,elementHeight,SVGWidth,SVGHeight) {
return Math.min(SVGWidth/elementWidth*0.8,SVGHeight/elementHeight*0.8);
}
var zoom = d3.zoom()
.on("zoom", function(d) {
g.attr("transform",d3.event.transform);
})
svg.call(zoom);
function zoomToRect(rect) {
// Bounds in local coordinates space:
var bounds = rect.getBBox();
// Corners of rect in local coordinate space:
var corners = [
{x: bounds.x,y: bounds.y},
{x: bounds.x + bounds.width, y: bounds.y},
{x: bounds.x + bounds.width, y: bounds.y + bounds.height },
{x: bounds.x, y: bounds.y + bounds.height }
];
// Reset transform:
g.attr("transform","");
// relevant transform:
var t = rect.getCTM();
// Convert the points to global SVG coordainte space:
for(var i = 0; i < corners.length; i++) {
var point = svg.node().createSVGPoint();
point.x = corners[i].x;
point.y = corners[i].y;
corners[i] = point.matrixTransform(t);
}
// get extents for x,y in global SVG space:
var x = d3.extent(corners, function(d) { return d.x; });
var y = d3.extent(corners, function(d) { return d.y; });
var w = x[1] - x[0];
var h = y[1] - y[0];
var k = scale(w,h,SVGwidth,SVGheight);
// Offset to center feature:
var ox = (SVGwidth - w*k)/2;
var oy = (SVGheight- h*k)/2;
var newTransform = d3.zoomIdentity
.translate(-x[0]*k+ox,-y[0]*k+oy)
.scale(k);
svg.call(zoom.transform,newTransform);
}
d3.select("button").on("click", function() { zoomToRect(d3.select("rect").node()) });svg {
border: 1px dotted black;
}
div {
position: absolute;
top: 0;
left: 0;
}<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width = "400" height = "180" style = "background: url("/images/001.jpg") no-repeat;">
<g id = "228" transform = "translate(2001,620)rotate(216) translate(0 -216)">
<g>
<rect x = "0" y = "0" height = "72" width = "36" stroke = "black" style = "fill: transparent;"></rect>
<text x = "0" y = "36" text-anchor = "start" style = "fill: black;">228</text>
</g>
<g>
<rect x = "36" y = "0" height = "72" width = "36" stroke = "black" style = "fill: transparent;">
</rect><text x = "36" y = "36" text-anchor = "start" style = "fill: black;">229</text>
</g>
</g>
<g id = "228" transform = "translate(1801,550)rotate(65) translate(0 0)">
<g>
<rect x = "0" y = "0" height = "100" width = "20" stroke = "black" style = "fill: transparent;"></rect>
<text x = "0" y = "36" text-anchor = "start" style = "fill: black;">454</text>
</g>
<g>
<rect x = "36" y = "0" height = "20" width = "100" stroke = "black" style = "fill: transparent;">
</rect><text x = "36" y = "36" text-anchor = "start" style = "fill: black;">455</text>
</g>
</g>
</svg>
<div><button>Zoom to 228</button></div>Вот фрагмент, который просто масштабируется без поведения масштабирования, переключаясь между четырьмя прямоугольниками, используя SVG плюс два бонусных прямоугольника:
var svg = d3.select("svg");
var contents = svg.html();
svg.selectAll("*").remove();
var g = svg.append("g")
.attr("transform", "translate(0,0)scale(1)")
.html(contents);
var SVGwidth = 400;
var SVGheight = 180;
// For getting scale factor:
var scale = function(elementWidth,elementHeight,SVGWidth,SVGHeight) {
return Math.min(SVGWidth/elementWidth*0.8,SVGHeight/elementHeight*0.8);
}
var current = 0;
function transition() {
g.selectAll("rect").each(function(_,i,n) {
if (i != current) return;
// Bounds in local coordinates space:
var bounds = n[i].getBBox();
// Corners of rect in local coordinate space:
var corners = [
{x: bounds.x,y: bounds.y},
{x: bounds.x + bounds.width, y: bounds.y},
{x: bounds.x + bounds.width, y: bounds.y + bounds.height },
{x: bounds.x, y: bounds.y + bounds.height }
];
// Hold original transform:
var oldTransform = g.attr("transform");
// Reset transform:
g.attr("transform","");
// relevant transform:
var t = n[i].getCTM();
// Convert the points to global SVG coordainte space:
for(var i = 0; i < corners.length; i++) {
var point = svg.node().createSVGPoint();
point.x = corners[i].x;
point.y = corners[i].y;
corners[i] = point.matrixTransform(t);
}
// get extents for x,y in global SVG space:
var x = d3.extent(corners, function(d) { return d.x; });
var y = d3.extent(corners, function(d) { return d.y; });
var w = x[1] - x[0];
var h = y[1] - y[0];
var k = scale(w,h,SVGwidth,SVGheight);
// Offset to center feature:
var ox = (SVGwidth - w*k)/2;
var oy = (SVGheight- h*k)/2;
var newTransform = "translate("+[-x[0]*k+ox,-y[0]*k+oy]+")scale("+k+")"
g.attr("transform",oldTransform)
.transition()
.on("start", function() {
d3.select(n[current]).style("fill","orange");
})
.duration(2000)
.attr("transform",newTransform)
.on("end", function() {
d3.select(n[current]).style("fill","none");
current++;
current = current%g.selectAll("rect").size();
transition();
})
})
}
transition();svg {
border: 1px dotted black;
}<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width = "400" height = "180" style = "background: url("/images/001.jpg") no-repeat;">
<g id = "228" transform = "translate(2001,620)rotate(216) translate(0 -216)">
<g>
<rect x = "0" y = "0" height = "72" width = "36" stroke = "black" style = "fill: transparent;"></rect>
<text x = "0" y = "36" text-anchor = "start" style = "fill: black;">228</text>
</g>
<g>
<rect x = "36" y = "0" height = "72" width = "36" stroke = "black" style = "fill: transparent;">
</rect><text x = "36" y = "36" text-anchor = "start" style = "fill: black;">229</text>
</g>
</g>
<g id = "228" transform = "translate(1801,550)rotate(65) translate(0 0)">
<g>
<rect x = "0" y = "0" height = "100" width = "20" stroke = "black" style = "fill: transparent;"></rect>
<text x = "0" y = "36" text-anchor = "start" style = "fill: black;">454</text>
</g>
<g>
<rect x = "36" y = "0" height = "20" width = "100" stroke = "black" style = "fill: transparent;">
</rect><text x = "36" y = "36" text-anchor = "start" style = "fill: black;">455</text>
</g>
</g>
</svg>Большое спасибо @andrew Reid.. Вы сэкономили мне много времени...
вы применили преобразование к <g>, но можем ли мы применить преобразование к <SVG>, потому что при применении преобразования фоновое изображение и прямоугольники не находятся точно в той же позиции, где они должны быть... фоновое изображение svg и подобное <g> должно быть синхронизированный инструмент