Найти центр SVG-групп или фигур

#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. Верно, именно это я и делал в своем исходном коде. Основной вывод из вашего кода для меня — отделить группы от фигур.