Как программно центрировать текстовые поля по родительским сгруппированным путям SVG с помощью XSLT

У меня есть приведенный ниже рисунок, сделанный из случайных фигур с различным количеством точек, к которому я могу добавить текстовые поля с помощью следующего XSLT. Решение, предложенное в этой ветке (то есть x = "50%" y = "50%" и dominant-baseline = "middle"text-anchor = "middle"), не работает, так как все такие текстовые поля оказываются в одном и том же положении рисунка, перекрываясь. Я действительно хотел бы, чтобы они были в центре каждого пути, в честь которого они названы. Вот рабочий пример, которая показывает поведение. Я уже спрашивал, можно ли этого добиться с помощью Javascript, но, поскольку преобразование будет выполняться с помощью макроса VBA, мне сказали, что это будет неправильным решением. По сути, мне нужно было бы заполнить поля x и y средней высотой и шириной путей, в которые должно вписываться каждое текстовое поле, эти держатели текста создаются в этой части кода:

      <text x = "" y = "" id = "{$id}-text" style = "-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000 " dominant-baseline = "middle" text-anchor = "middle">
        <tspan id = "{$id}-tspan" x = "" y = "">
          <xsl:value-of select = "$id"/>
        </tspan>
      </text>

SVG

<svg xmlns = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" id = "exportSvg" width = "400" height = "400">
    <defs/>
    <rect width = "400" height = "400" transform = "translate(0, 0)" fill = "rgb(255, 255, 255)" style = "fill:rgb(255, 255, 255);"/>
    <g>
        <g id = "Drawing-svg" clip-path = "url(#rect-mask-Drawing)">
            <clipPath id = "rect-mask-Drawing">
                <rect x = "0" y = "0" width = "400" height = "400"/>
            </clipPath>
            <g id = "chart-svg">
                <g id = "svg-main" clip-path = "url(#rect-mask-Main)">
                    <clipPath id = "rect-mask-Main">
                        <rect x = "0" y = "0" width = "400" height = "400"/>
                    </clipPath>
                    <g id = "Drawing-svg">
                        <g id = "Parts-svg">
                            <g id = "Section-svg">
                                <g id = "Item1-svg">
                                    <path d = "M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z" stroke = "#000000" style = "fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id = "Item1"/>
                                </g>
                                <g id = "Item2-svg">
                                    <path d = "M 198.06872,89.614437 -9.21291,31.643703 -23.42303,34.67823 51.52002,20.68699 47.20879,-57.62707 z" stroke = "#000000" style = "fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id = "Item2"/>
                                </g>
                                <g id = "Item3-svg">
                                    <path d = "M 161.0455,182.56778 -41.68122,-5.64443 15.98375,27.05111 67.62172,3.73783 32.80201,-13.55927 z" stroke = "#000000" style = "fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id = "Item3"/>
                                </g>
                            </g>
                        </g>
                    </g>
                </g>
            </g>
        </g>
    </g>
</svg>

XSLT

<?xml version = "1.0" encoding = "utf-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
    xmlns:svg = "http://www.w3.org/2000/svg"
    xmlns = "http://www.w3.org/2000/svg"
    exclude-result-prefixes = "svg"
    version = "1.0">
<xsl:output method = "xml" encoding = "utf-8" omit-xml-declaration = "yes"/>
  
  <xsl:strip-space elements = "*"/>
  
  <xsl:template match = "svg:g[@id[starts-with(., 'Item')]]">
    <xsl:copy>
      <xsl:apply-templates select = "@* | node()"/>
      <xsl:variable name = "id" select = "substring-before(@id, '-')"/>
      <text x = "" y = "" id = "{$id}-text" style = "-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000 " dominant-baseline = "middle" text-anchor = "middle">
        <tspan id = "{$id}-tspan" x = "" y = "">
          <xsl:value-of select = "$id"/>
        </tspan>
      </text>
    </xsl:copy>
  </xsl:template>
  

  
  
 <xsl:template match = "processing-instruction('xml-stylesheet')"/>
 
  <xsl:template match = "@* | node()">
    <xsl:copy>
      <xsl:apply-templates select = "@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>
Создание фильтров для вашего сайта
Создание фильтров для вашего сайта
Фильтры - удобный инструмент в арсенале веб-дизайнера. Они позволяют изменять элементы на странице с помощью всего нескольких строк кода. Эти...
Анимация SVG-узоров без единой строки CSS
Анимация SVG-узоров без единой строки CSS
Недавно я работал над веб-проектом, который позволил мне поэкспериментировать с шаблонами SVG. С SVG очень приятно работать, как только вы получите...
Как использовать d3.js для рисования 2D SVG-элементов в приложении Angular?
Как использовать d3.js для рисования 2D SVG-элементов в приложении Angular?
D3.js - это обширная библиотека, используемая для привязки произвольных данных к объектной модели документа (DOM). Мы разберем основные варианты...
1
0
80
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Следующие шаблоны преобразуют путь, например

<path d = "M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z"/>

в спецификацию его центра, например

<center x = "137.5" y = "122.7712355"/>

Они делают определенные предположения о запятых и пробелах в пути, но их можно легко адаптировать. Также они используют нестандартную функцию node-set, которая поддерживается почти всеми процессорами XSLT 1.0, только под другими именами, например exslt:node-set.

<xsl:template match = "path">
  <xsl:copy-of select = "."/>  <!-- copy the <path> element -->
  <xsl:call-template name = "bbox">
    <xsl:with-param name = "path" select = "substring-before(substring-after(@d,'M '),' Z')"/>
    <xsl:with-param name = "bbox">
      <bbox x = "10000" X = "-10000" y = "10000" Y = "-10000"/>
    </xsl:with-param>
  </xsl:call-template>
</xsl:template>
<xsl:template name = "bbox">
  <xsl:param name = "path"/>
  <xsl:param name = "bbox"/>
  <xsl:variable name = "b" select = "node-set($bbox)/*"/>
  <xsl:choose>
    <xsl:when test = "$path">
      <xsl:variable name = "x" select = "number(substring-before($path,','))"/>
      <xsl:variable name = "y" select = "number(substring-after(substring-before($path,' '),','))"/>
      <xsl:variable name = "next" select = "substring-after($path,' ')"/>
      <xsl:call-template name = "bbox">
        <xsl:with-param name = "path" select = "$next"/>
        <xsl:with-param name = "bbox">
          <bbox>
            <xsl:attribute name = "x">
              <xsl:choose>
                <xsl:when test = "$x &lt; $b/@x">
                  <xsl:value-of select = "$x"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:value-of select = "$b/@x"/>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:attribute>
            <xsl:attribute name = "X">
              <xsl:choose>
                <xsl:when test = "$x &gt; $b/@X">
                  <xsl:value-of select = "$x"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:value-of select = "$b/@X"/>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:attribute>
            <xsl:attribute name = "y">
              <xsl:choose>
                <xsl:when test = "$y &lt; $b/@y">
                  <xsl:value-of select = "$y"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:value-of select = "$b/@y"/>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:attribute>
            <xsl:attribute name = "Y">
              <xsl:choose>
                <xsl:when test = "$y &gt; $b/@Y">
                  <xsl:value-of select = "$y"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:value-of select = "$b/@Y"/>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:attribute>
          </bbox>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <center x = "{($b/@x + $b/@X) div 2}" y = "{($b/@y + $b/@Y) div 2}"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

В вашем конкретном случае замените элемент <center> текстом, который вы хотите в этих координатах:

<xsl:variable name = "id" select = "substring-before(parent::g/@id,'-')"/>
<text x = "{($b/@x + $b/@X) div 2}" y = "{($b/@y + $b/@Y) div 2}" id = "{$id}-text">
  <tspan id = "{$id}-tspan" x = "{($b/@x + $b/@X) div 2}" y = "{($b/@y + $b/@Y) div 2}">
    <xsl:value-of select = "$id"/>
  </tspan>
</text>

спасибо, простите мое невежество, но как мне интегрировать это в вышеизложенное? Я пытался поместить до или после блока, создающего текстовые поля, но ничего не произошло, я также попытался создать другую таблицу стилей, которая преобразует файл, полученный в результате предыдущего преобразования.

Bradipo 26.10.2022 20:15

Замените элемент <center> тем, что вы хотите вывести для данного элемента <path>: возможно, путем и связанным текстовым элементом.

Heiko Theißen 26.10.2022 20:25

Я смиренно признаюсь, что не знаю, как... Я пробовал с <text match = "svg:g[@id[starts-with(., 'Item')]]" x = "{($c/@x + $c/@X) div 2}" y = "{($c/@y + $c/@Y) div 2}"/>, но это не совсем правильно.

Bradipo 26.10.2022 21:20

Смотрите мой дополненный ответ.

Heiko Theißen 27.10.2022 07:29
Ответ принят как подходящий

Я сделал попытку, ниже.

На самом деле это довольно сложно сделать в XSLT 1, потому что синтаксический анализ значения svg:path/@d требует большой обработки строк и рекурсии.

Я написал именованный шаблон get-bounding-box-edge-value, который вы вызываете с 2 параметрами; строка, содержащая список координат x,y и строку, указывающую, какое ребро вы хотите найти (либо «ВЕРХ», «НИЗ», «ЛЕВО» или «ПРАВО»). Шаблон рекурсивно вызывает сам себя для обработки списка и возвращает минимальную координату y для 'ВЕРХ', максимальную y для 'НИЗ', минимальную х для 'ЛЕВО' и максимальную х для 'ПРАВО'.

Затем при обработке svg:path я вызываю этот шаблон 4 раза, чтобы получить 4 ребра, которые определяют ограничивающую рамку пути, вычислить центральную точку из этих 4 значений и поместить элемент svg:text в эту точку, установив атрибуты dominant-baseline и text-anchor так что текстовое содержание svg:text сосредоточено вокруг этой точки.

<?xml version = "1.0" encoding = "utf-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" xmlns:svg = "http://www.w3.org/2000/svg" xmlns = "http://www.w3.org/2000/svg" exclude-result-prefixes = "svg" version = "1.0">
   <xsl:output method = "xml" encoding = "utf-8" omit-xml-declaration = "yes" indent = "yes"/>
    
   <xsl:template name = "get-bounding-box-edge-value">
      <xsl:param name = "which-edge"/>
      <xsl:param name = "coordinate-path"/>
      <xsl:variable name = "next-coordinate-pair" select = "substring-before($coordinate-path, ' ')"/>
      <xsl:variable name = "remaining-coordinates" select = "substring-after($coordinate-path, ' ')"/>
      <!-- get the next value (either an x or y coordinate) from the first of the list of coordinate pairs -->
      <xsl:variable name = "next-value">
         <xsl:choose>
            <xsl:when test = "$which-edge='TOP' or $which-edge='BOTTOM'">
               <!-- we want the Y coordinate -->
               <xsl:value-of select = "substring-after($next-coordinate-pair, ',')"/>
            </xsl:when>
            <xsl:otherwise>
               <!-- we want the X coordinate -->
               <xsl:value-of select = "substring-before($next-coordinate-pair, ',')"/>
            </xsl:otherwise>
         </xsl:choose>
      </xsl:variable>
      <xsl:choose>
         <xsl:when test = "$remaining-coordinates">
            <xsl:variable name = "remaining-edge-value">
               <xsl:call-template name = "get-bounding-box-edge-value">
                  <xsl:with-param name = "which-edge" select = "$which-edge"/>
                  <xsl:with-param name = "coordinate-path" select = "$remaining-coordinates"/>
               </xsl:call-template>
            </xsl:variable>
            <xsl:choose>
               <xsl:when test = "$which-edge='TOP' or $which-edge='LEFT'">
                  <!-- we're calculating the minimum value (NB 0,0 is the upper-left corner) -->
                  <xsl:choose>
                     <xsl:when test = "number($next-value) &lt; number($remaining-edge-value)">
                        <xsl:value-of select = "$next-value"/>
                     </xsl:when>
                     <xsl:otherwise>
                        <xsl:value-of select = "$remaining-edge-value"/>
                     </xsl:otherwise>
                  </xsl:choose>
               </xsl:when>
               <xsl:otherwise>
                  <!-- we're calculating the maximum value -->
                  <xsl:choose>
                     <xsl:when test = "number($next-value) &gt; number($remaining-edge-value)">
                        <xsl:value-of select = "$next-value"/>
                     </xsl:when>
                     <xsl:otherwise>
                        <xsl:value-of select = "$remaining-edge-value"/>
                     </xsl:otherwise>
                  </xsl:choose>
               </xsl:otherwise>
            </xsl:choose>
         </xsl:when>
         <xsl:otherwise>
            <!-- there are no more coordinates in the path - this is the last coordinate pair -->
            <!-- so we just return the value taken from this coordinate pair -->
            <xsl:value-of select = "$next-value"/>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>
    
   <xsl:template match = "svg:g[starts-with(@id, 'Item')]">
      <!-- calculate the bounding box of the path by extracting the TOP, LEFT, RIGHT, and BOTTOM coordinate value-->
      <xsl:variable name = "coordinate-path" select = "translate(substring-after(svg:path/@d, 'M '), 'zZ', '')"/>
      <xsl:variable name = "top">
         <xsl:call-template name = "get-bounding-box-edge-value">
            <xsl:with-param name = "which-edge">TOP</xsl:with-param>
            <xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
         </xsl:call-template>
      </xsl:variable>
      <xsl:variable name = "left">
         <xsl:call-template name = "get-bounding-box-edge-value">
            <xsl:with-param name = "which-edge">LEFT</xsl:with-param>
            <xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
         </xsl:call-template>
      </xsl:variable>
      <xsl:variable name = "bottom">
         <xsl:call-template name = "get-bounding-box-edge-value">
            <xsl:with-param name = "which-edge">BOTTOM</xsl:with-param>
            <xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
         </xsl:call-template>
      </xsl:variable>
      <xsl:variable name = "right">
         <xsl:call-template name = "get-bounding-box-edge-value">
            <xsl:with-param name = "which-edge">RIGHT</xsl:with-param>
            <xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
         </xsl:call-template>
      </xsl:variable>
      <!-- calculate the coordinates of the centroid -->
      <xsl:variable name = "center-x" select = "(number($left) + number($right)) div 2"/>
      <xsl:variable name = "center-y" select = "(number($top) + number($bottom)) div 2"/>
      <xsl:copy>
         <xsl:apply-templates select = "@* | node()"/>
         <xsl:variable name = "id" select = "substring-before(@id, '-')"/>
         <text x = "50%" y = "50%" id = "{$id}-text" style = "
            -inkscape-font-specification:'Calibri, Normal';
            font-family:Calibri;font-weight:normal;font-style:normal;
            font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;
            font-variant-caps:normal;font-variant-numeric:normal;
            font-variant-east-asian:normal;
            fill:#000000;
            text-align:center
         ">
            <tspan id = "{$id}-tspan" x = "{$center-x}" y = "{$center-y}" dominant-baseline = "middle" text-anchor = "middle">
               <xsl:value-of select = "$id"/>
            </tspan>
         </text>
      </xsl:copy>
   </xsl:template>
    
   <xsl:template match = "@* | node()">
      <xsl:copy>
         <xsl:apply-templates select = "@* | node()"/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

Обратите внимание, что это не будет анализировать какое-либо значение path/@d в произвольном SVG, но он обрабатывает ваши входные данные, которые используют только команды M (moveto) и Z (закрыть) (и z, что означает то же, что и Z). Из вашего комментария о том, как генерируется SVG, я предполагаю, что это, вероятно, безопасно. Но для произвольных путей вам также нужно будет обрабатывать другие команды, такие как H (горизонтальные линии), C (кривые) и т. д., и этот именованный шаблон должен быть более сложным.

Conal Tuohy 27.10.2022 16:17

Спасибо, я на самом деле имел в виду всевозможные пути, совершенно произвольные и с кривыми Безье, но, поскольку в приведенном выше коде это было неясно, я принял ответ. В любом случае это полезно в случае полигонов, хотя в моем случае мне придется найти способ настроить его. Будет ли это намного сложнее?

Bradipo 28.10.2022 07:38

Теперь я понимаю, что должен был потратить больше времени на размышления о том, как задать вопрос, есть много переменных, которые я не рассматривал как объяснение, должен ли я переформулировать или задать еще одну, если я не справлюсь?

Bradipo 28.10.2022 08:22

Было бы немного сложнее, да. Сложность заключается в разборе этой @d «командной» строки, потому что ее синтаксис имеет различные дополнительные функции; например вы можете сказать «M0,0 M0,1 Z» или вы можете сказать «M 0,0 0,1 z», и они означают одно и то же (вторая команда «M» избыточна, потому что она следует за предыдущей). Таким образом, этот именованный шаблон должен иметь еще один параметр для указания «текущей команды», чтобы он знал, какая команда выполняется в случае, если она не была указана явно, и еще два параметра для хранения «текущей позиции», чтобы он мог вычислить относительные координаты

Conal Tuohy 28.10.2022 11:23

(команды, указанные строчными буквами, используют относительные координаты, тогда как прописные буквы используют абсолютные координаты). Другие команды также имеют разное количество параметров; команды горизонтальной и вертикальной линии задают только одну точку, а команды Безье задают контрольные точки... я предполагаю, что этот именованный шаблон, вероятно, удвоится в размере.

Conal Tuohy 28.10.2022 11:27

Другие вопросы по теме