#javascript #svg
#javascript #svg
Вопрос:
Мне нужно расположить маркеры (круги) в центре комнат на плане этажа SVG. Каждая комната может быть либо отдельной формой, например прямоугольником, либо группой фигур. Круг должен занимать около 1/4 комнаты: радиус = (ширина высота) / 8
svg необходимо изменить в соответствии с его контейнером div.
После множества проб и ошибок я придумал код, который, кажется, работает, но также выглядит слишком сложным, поскольку я использую getBBox(), getBoundingClientRect() и MatrixTransform():
- getBBox дает мне правильную ширину и высоту, но x = y = 0 для элементов группы
- getBoundingClientRect MatrixTransform дает мне правильные координаты cx и cy, но не правильную ширину и высоту комнаты.
Есть ли более простой способ?
const draw = document.getElementsByTagName("svg")[0];
const NS = draw.getAttribute("xmlns");
// Get titles
const titleElements = draw.getElementsByTagNameNS(NS, "title");
Array.from(titleElements).forEach((titleElement) => {
let parentElement = titleElement.parentElement;
let clientRect = parentElement.getBoundingClientRect();
let bbox = parentElement.getBBox();
let point = draw.createSVGPoint();
point.x = clientRect.left clientRect.width / 2;
point.y = clientRect.top clientRect.height / 2;
// Convert screen coordinates into SVG document coordinates
const svgP = point.matrixTransform(draw.getScreenCTM().inverse());
// clientRect doesn't resize with the svg
// let radius = (clientRect.width clientRect.height) / 8;
// bbox resizes with the svg
let radius = (bbox.width bbox.height) / 8;
let circle = document.createElementNS(NS, "circle");
circle.setAttribute("cx", svgP.x.toString());
circle.setAttribute("cy", svgP.y.toString());
circle.setAttribute("r", radius.toString());
circle.setAttribute("fill", "cyan");
draw.appendChild(circle);
});
<!DOCTYPE html>
<html>
<head>
<title>Floor Plan</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app" style="width:300px;height:300px;">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
viewBox="0 0 792 612"
xml:space="preserve"
color-interpolation-filters="sRGB"
class="st7"
>
<g>
<g id="group1-1" transform="translate(659.691,-186.346) rotate(60)">
<title>Space.1001</title>
<g id="shape1-2">
<rect x="0" y="508.5" width="90" height="90" class="st1" />
</g>
</g>
<g id="group1000-10" transform="translate(482.508,-114.75) rotate(60)">
<title>Space.1000</title>
<g id="shape1000-11">
<rect x="0" y="208.5" width="90" height="90" class="st4" />
</g>
</g>
<g id="group1002-18" transform="translate(659.691,-67.0962) rotate(60)">
<title>Space.1002</title>
<g id="shape1002-19">
<rect x="0" y="508.5" width="90" height="90" class="st5" />
</g>
</g>
<rect x="0" y="208.5" width="90" height="90" class="st6">
<title>Space.1004</title>
</rect>
</g>
</svg>
</div>
</body>
</html>
Комментарии:
1. Ваш код в основном правильный. Либо используйте
getBoundingClientRect()
и преобразуйте сgetScreenCTM().inverse()
помощью , либо используйтеgetBBox()
и преобразуйте сgetCTM().inverse()
помощью .2. Проблема в том, что я использую getBoundingClientRect «и»getBBox, а не «или». Вот почему я подозреваю, что должен быть лучший способ. Но если я попробую только одно или другое, это не удастся.
3. ваш код присваивает
bbox
, но никогда не использует его.4. Я исправил код и перефразировал свой вопрос. Спасибо @ccprog!
5. а. Я вижу свою ошибку.
.getCTM()
возвращает преобразование к исходному видовому экрану, а не к исходной системе координат. Тогда лучший способ — действительно переместить круг внутри преобразованной группы, согласно ответу Пола.
Ответ №1:
Вот некоторый код, который немного проще.
const draw = document.getElementsByTagName("svg")[0];
const NS = draw.namespaceURI;
// Do the rooms that are groups
let rooms = document.querySelectorAll("title g");
rooms.forEach(room => {
room.appendChild( makeDot(room.getBBox()) );
});
// Do the rooms where the title is in a rect
let titles = document.querySelectorAll("rect > title");
titles.forEach(title => {
let rect = title.parentElement;
rect.insertAdjacentElement('afterend', makeDot(rect.getBBox()) );
});
// Make the circle element
function makeDot(roomBounds)
{
let radius = (roomBounds.width roomBounds.height) / 8;
let circle = document.createElementNS(NS, "circle");
circle.setAttribute("cx", roomBounds.x roomBounds.width / 2);
circle.setAttribute("cy", roomBounds.y roomBounds.height / 2);
circle.setAttribute("r", radius);
circle.setAttribute("fill", "cyan");
return circle;
}
<div id="app" style="width:300px;height:300px;">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
viewBox="0 0 792 612"
xml:space="preserve"
color-interpolation-filters="sRGB"
class="st7"
>
<g>
<g id="group1-1" transform="translate(659.691,-186.346) rotate(60)">
<title>Space.1001</title>
<g id="shape1-2">
<rect x="0" y="508.5" width="90" height="90" class="st1" />
</g>
</g>
<g id="group1000-10" transform="translate(482.508,-114.75) rotate(60)">
<title>Space.1000</title>
<g id="shape1000-11">
<rect x="0" y="208.5" width="90" height="90" class="st4" />
</g>
</g>
<g id="group1002-18" transform="translate(659.691,-67.0962) rotate(60)">
<title>Space.1002</title>
<g id="shape1002-19">
<rect x="0" y="508.5" width="90" height="90" class="st5" />
</g>
</g>
<rect x="0" y="208.5" width="90" height="90" class="st6">
<title>Space.1004</title>
</rect>
</g>
</svg>
</div>
Комментарии:
1. Спасибо! Имеет смысл иметь специальный код для групп. Будет ли код для rect работать с любой формой? Как упоминалось в моем вопросе, прямоугольник — это всего лишь пример. У меня могут быть полигоны, пути и т. Д.
2. ДА. Но обратите внимание, что при этом точка будет помещена в центр ограничивающей рамки фигур, а не в центр фигуры. Если контур или многоугольник имеет контур странной формы, то точка может оказаться за пределами фигуры. Поиск оптического центра контура произвольной формы — гораздо более сложная задача.
3. Кроме того, insertAdjacentElement может быть проблемой для меня, поскольку план может иметь несколько слоев, и мне нужны точки поверх всего. Но я понял идею.
4. Тогда это может стать проблемой. Причина, по которой я вставляю их туда, где они есть, заключается в том, что на них влияют любые
transform
s, которые могут быть в элементах-предках. Если вам нужно вставить их на совершенно отдельный слой в другом месте SVG, тогда вам нужно каким-то образом применить сопоставимое преобразование к кругу.5. Верно, именно это я и делал в своем исходном коде. Основной вывод из вашего кода для меня — отделить группы от фигур.