Formatting Objects (FO) / Erzeugen von skalierbaren Vektor-Grafiken (SVG)

Erzeugen von skalierbaren Vektor-Grafiken (SVG)

Erzeugen von skalierbaren Vektor-Grafiken (SVG)

➪ Skalierbare Vektor-Grafiken (SVG) sind pures XML mit einem speziellen Namespace, der die Funktionalitäten definiert, auf der u.a. auch Browser arbeiten, die SVG-Dateien anzeigen können. Spezielle Grafikelemente (Text, Rechteck, Kreis, Linie, ...) können mit Koordinaten und weiteren Eigenschaften ausgestattet werden, die ein Anzeigen als Grafik möglich machen.

Auf dieser Seite:

SVG: <polyline>

In <polyline> können die Eckpunkte über X- und Y-Koordinaten definiert werden.


<svg xmlns="http://www.w3.org/2000/svg" width="380" height="167">
	<rect x="5" 
              y="5" width="375" 
              height="162" 
              style="fill:none; stroke:black"/>
	<text x="110" 
              y="20" 
              style="font-size:7pt; fill:blue;"
        >Zick-zack-Kurs</text>
	<polyline 
              style="fill:none;stroke:green;stroke-width:5;" 
	      points="20 11 
	      133 119 
	      247 50 
	      360 100" />
</svg>

pic/mypolyline.svg

Das Balkendiagramm

Verhältnismäßig einfach ist das Erstellen von Balkendiagrammen. Dabei ist für das Verständnis wichtig, dass sich die Null-Koordinate nicht unten links auf einer Zeichenfläche, sondern oben links befindet.


<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="xs fn">
 <xsl:output method="xml" 
      version="1.0" 
      encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <xsl:result-document 
         href="../output/Balkendiagramm.svg">
      <xsl:variable 
           name="vheight" 
           select="count(Orte/Ort/Mensch) * 15 + 130"/>
       <svg:svg 
            xmlns:svg="http://www.w3.org/2000/svg" 
            width="380" 
            height="{$vheight}" 
            style="fill:gray;">
        <svg:rect x="5" y="5" 
             width="375" 
             height="{$vheight - 5}" 
             style="fill:none; stroke:black"/>
        <svg:text x="50" y="50">Einkommen</svg:text>   
        <xsl:for-each select="/Orte/Ort/Mensch">
          <xsl:sort 
               select="Gehalt" 
               data-type="number" 
               order="ascending"/>
          <svg:text x="20" y="{105 + position() *15}">
            <xsl:value-of select="vorname"/>
          </svg:text>
          <svg:rect x="100" 
            y="{100 + position() *15}"
            width="{Gehalt * 0.04}"
            height="5"
            style="fill:blue"/>
        </xsl:for-each>  
      </svg:svg>
    </xsl:result-document>
  </xsl:template>  
</xsl:stylesheet>

pic/Balkendiagramm.svg

Das Säulendiagramm

Schon etwas komplexer wird es bei der Generierung von Säulendiagrammen, da (wie erwähnt) die Null-Koordinate der Zeichenfläche sich oben links auf der Zeichenfläche befindet. Dadurch muss erst einmal eine Koordinatentransformation durchgeführt werden, um die Basislinie auf eine Ebene zu bekommen. Zu Darstellungszwecken ist es ggf. auch wünschenswert, die Textlegende in senkrechter Schrift zu realisieren.


<xsl:stylesheet version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  exclude-result-prefixes="xs fn">
 <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>  
  <xsl:template match="/">
    <xsl:result-document href="../output/Saeulendiagramm.svg">
      <xsl:variable name="zoomfaktor">0.05</xsl:variable>
      <xsl:variable name="vAbstandVonOben">20</xsl:variable>
      <xsl:variable name="vAbstandVonUnten">70</xsl:variable>
      <xsl:variable name="vmaxGehalt" 
           select="max(Orte/Ort/Mensch/Gehalt)"/>
      <xsl:variable name="vwidth" 
           select="count(Orte/Ort/Mensch) * 15 + 50"/>
      <xsl:variable name="vheight" 
           select="($vmaxGehalt * $zoomfaktor) 
                    + $vAbstandVonOben + $vAbstandVonUnten"/>
      <svg:svg xmlns:svg="http://www.w3.org/2000/svg" 
               width="{$vwidth}" 
               height="{$vheight}" 
               style="fill:gray;">
        <svg:rect x="5" 
                  y="5" 
                  width="{$vwidth - 5}" 
                  height="{$vheight - 5}" 
                  style="fill:none; stroke:black"/>
        <svg:text x="50" y="50">Einkommen</svg:text>   
        <xsl:for-each select="/Orte/Ort/Mensch">
          <xsl:sort select="Gehalt" 
                    data-type="number" 
                    order="ascending"/>
          <svg:text x="{20 + position() *15}" 
                    y="{$vheight - $vAbstandVonUnten + 10}" 
                    writing-mode="tb">
            <xsl:value-of select="vorname"/>
          </svg:text>
          <svg:rect x="{20 + position() *15}" 
                    y="{(($vmaxGehalt - Gehalt) * $zoomfaktor)
                        + $vAbstandVonOben}"
                    width="5"
                    height="{Gehalt * $zoomfaktor}"
                    style="fill:blue"/>
        </xsl:for-each>  
      </svg:svg>
    </xsl:result-document>
  </xsl:template>
</xsl:stylesheet>

pic/Saeulendiagramm.svg

Das Kreisdiagramm

Noch einmal komplexer wird es bei der automatisierten Erstellung von Kreisdiagrammen, weil die Konfiguration der Kreis-Segmente keineswegs trivial ist und zudem auch unterschiedliche Farbgebungen sowie die dazu passende Legende relevant werden. Zum besseren Verständnis macht es Sinn, mit mehreren Variablen zu arbeiten, die z.B. die extern definierten Farbschemata einbinden oder auch mit einer vorsortierten Liste arbeiten.


<xsl:stylesheet version="2.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
 <xsl:template name="CreateKreisdiagramm">
  <xsl:param name="pwidth">630</xsl:param>
  <xsl:variable name="pheight">450</xsl:variable>
  <xsl:variable name="vcntGesamt">
   <xsl:value-of select="count(/Orte/Ort/Mensch)"/>
  </xsl:variable>
  <xsl:variable name="farbliste_svg" 
       select="document('../input/Farbliste.xml')"/>
  <xsl:variable name="sortierteListe">
   <SORTIERTELISTE>
    <xsl:for-each select="/Orte/Ort/Mensch">
     <xsl:sort select="Gehalt" 
          data-type="number" 
          order="descending"/>
     <Mensch>
      <vorname>
       <xsl:value-of select="vorname"/>
      </vorname>
      <name>
       <xsl:value-of select="name"/>
      </name>
      <Gehalt>
       <xsl:value-of select="Gehalt"/>
      </Gehalt>
     </Mensch>
    </xsl:for-each>
   </SORTIERTELISTE>
  </xsl:variable>
  <xsl:variable name="vGesamtgehalt" 
       select="sum($sortierteListe/SORTIERTELISTE/Mensch/Gehalt)"/>
  <xsl:result-document 
       href="../output/Kreisdiagramm.svg" 
       encoding="ISO-8859-1"
       indent="yes">
   <svg width="{$pwidth}" height="{$pheight}" 
        xmlns="http://www.w3.org/2000/svg" 
        version="1.1"
        baseProfile="tiny" 
        xml:lang="de" 
        fill="none">
    <rect x="5" 
          y="5" 
          width="{$pwidth - 5}" 
          height="{$pheight - 5}"
          style="fill:none; stroke:black;"/>
    <title>Einkommen pro Person</title>
    <svg 
        baseProfile="tiny" 
        viewBox="-35 -35 70 70" 
        width="{$pheight - 100}"
        height="{$pheight - 100}" 
        y="25" 
        x="25">
     <circle r="32.831" 
             fill="{$farbliste_svg/FARBEN/farbe[1]/@hex}"
             stroke="black" 
             title="{$sortierteListe/SORTIERTELISTE/Mensch[1]/name}"/>
     <xsl:for-each 
          select="$sortierteListe/SORTIERTELISTE/Mensch">
      <xsl:variable name="pos" select="position()"/>
      <xsl:if test="$pos &gt; 1">
       <xsl:variable name="vbisher">
        <xsl:value-of 
             select="sum(preceding-sibling::*/Gehalt)"/>
       </xsl:variable>
       <xsl:variable name="vAnteil">
        <xsl:value-of 
             select="$vbisher * 100 div $vGesamtgehalt"/>
       </xsl:variable>
       <xsl:variable name="vpendant">
        <xsl:value-of select="100 - $vAnteil"/>
       </xsl:variable>
       <circle r="15.9155" stroke-width="31.831"
        stroke-dasharray="0, {$vAnteil}, {$vpendant}, 0"
        title="{concat(vorname, ' ', name)}">
        <xsl:attribute name="stroke">
         <xsl:value-of 
              select="$farbliste_svg//FARBEN/farbe[$pos]/@hex"/>
        </xsl:attribute>
       </circle>
      </xsl:if>
     </xsl:for-each>
     <circle r="15.3" fill="black" title="Innenkreis Rand"/>
     <circle r="15" fill="white" title="Innenkreis"/>
     <line x2="32" stroke-linecap="round" 
           stroke-width="1" stroke="red" 
           transform="rotate(0)"/>
    </svg>
    <xsl:for-each 
         select="$sortierteListe/SORTIERTELISTE/Mensch">
     <xsl:variable name="pos" select="position()"/>
     <rect x="400" 
           y="{($pos * 20)}" 
           width="10" 
           height="10"
           style="fill:{$farbliste_svg//FARBEN/farbe[$pos]/@hex}; 
                  stroke:{$farbliste_svg//FARBEN/farbe[$pos]/@hex};"
           title="{name}"/>
     <text x="430" 
           y="{$pos * 20 + 10}" 
           font-family="Verdana,sans-serif" 
           font-size="9px"
           fill="black">
      <xsl:value-of select="vorname"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="name"/>
     </text>
    </xsl:for-each>
    <text x="50" 
          y="{$pheight - 20}" 
          font-family="Verdana,sans-serif" 
          font-size="19px"
          font-weight="bold" 
          fill="black">Einkommen pro Person</text>
   </svg>
  </xsl:result-document>
 </xsl:template>
 <xsl:template match="/">
  <xsl:call-template name="CreateKreisdiagramm"/>
 </xsl:template>
</xsl:stylesheet>

Das vorstehende XSL-Stylesheet für das Kreisdiagramm greift auf diese Farbliste ('../input/Farbliste.xml') zurück:


<?xml version="1.0" encoding="UTF-8"?>
<FARBEN>
  <farbe hex="#138808" name="India green"/>
  <farbe hex="#177245" name="Dark spring green"/>
  <farbe hex="#059033" name="North Texas Green"/>
  <farbe hex="#03C03C" name="Dark pastel green"/>
  <farbe hex="#CEFF00" name="Volt"/>
  <farbe hex="#FDFD96" name="Pastel yellow"/>
  <farbe hex="#FFFF66" name="Laser Lemon"/>
  <farbe hex="#FFFF33" name="Electric yellow"/>
  <farbe hex="#FEDF00" name="Yellow (Pantone)"/>
  <farbe hex="#FB9902" name="Orange (RYB)"/>
  <farbe hex="#FD0E35" name="Scarlet"/>
  <farbe hex="#FF1493" name="deeppink"/>
  <farbe hex="#FD6C9E" name="French pink"/>
  <farbe hex="#FFC0CB" name="pink"/>
  <farbe hex="#FDD5B1" name="Feldspar"/>
  <farbe hex="#CC99CC" name="Light grayish magenta"/>
  <farbe hex="#CC99FF" name="Pale violet"/>
  <farbe hex="#871F78" name="darkpurple"/>
  <farbe hex="#6F2DA8" name="Grape"/>
  <farbe hex="#6C3082" name="Eminence"/>
  <farbe hex="#66023C" name="Imperial purple"/>
  <farbe hex="#000080" name="navy"/>
  <farbe hex="#FE28A2" name="Persian rose"/>
  <farbe hex="#FDBCB4" name="Melon"/>
  <farbe hex="#6F00FF" name="Electric indigo"/>
  <farbe hex="#6E6EF9" name="Dark imperial blue"/>
  <farbe hex="#87CEFA" name="lightskyblue"/>
  <farbe hex="#66DDAA" name="Medium aquamarine"/>
  <farbe hex="#BCD4E6" name="Beau blue"/>
  <farbe hex="#B0E0E6" name="powderblue"/>
  <farbe hex="#E6E6FA" name="lavendel"/>
  <farbe hex="#CCFF00" name="Electric lime"/>
  <farbe hex="#a6d608" name="Vivid lime green"/>
  <farbe hex="#987654" name="Pale brown"/>
  <farbe hex="#965A3E" name="Coconut"/>
  <farbe hex="#778BA5" name="Shadow blue"/>
  <farbe hex="#74C365" name="Mantis"/>
  <farbe hex="#76FF7A" name="Screaming Green"/>
  <farbe hex="#664C28" name="Donkey brown"/>
  <farbe hex="#663399" name="Rebecca Purple"/>
  <farbe hex="#000000" name="black"/>
</FARBEN>

Und so siehts aus:

pic/Kreisdiagramm.svg

Hübsch, nicht?

Skalierungen

Ein häufiges Problem ist die automatisierte Datenvisualisierung mit SVG, wenn Minimal-, Maximal- oder logarithmische Werte berechnet werden sollen und der verwendete XSL-Prozessor diesbezügliche Funktionen nicht unterstützt. In diesem Fall kann die Einbindung von Extensions hilfreich sein. Hier finden Sie eine XSL-FO-Logik mit eingebetteter SVG-Grafik für logarithmische Skalierung.


<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:math="http://exslt.org/math"
  extension-element-prefixes="math">
  <xsl:variable name="vmaxGehalt">
    <xsl:value-of 
      select="//Gehalt[not(
      following::Gehalt &gt; . 
      or 
      preceding::Gehalt &gt; .)]"/>
  </xsl:variable>
  <xsl:variable name="vminGehalt">
    <xsl:value-of 
      select="//Gehalt[not(
      following::Gehalt &lt; . 
      or 
      preceding::Gehalt &lt; .)]"/>
  </xsl:variable>
  <xsl:variable name="vheight" >360</xsl:variable>
  <xsl:variable name="vzoomfaktor">0.05</xsl:variable>  
  <xsl:variable name="vAbstandVonOben">20</xsl:variable>
  <xsl:variable name="vAbstandVonUnten">70</xsl:variable>
  <xsl:variable name="vAbstandVonLinks">50</xsl:variable>
  <xsl:template match="/">    
    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
      <fo:layout-master-set>
        <fo:simple-page-master 
            master-name="a" 
            page-height="297mm" 
            page-width="21cm" 
            margin="10mm">
          <fo:region-body/>
        </fo:simple-page-master>
      </fo:layout-master-set>
      <fo:page-sequence master-reference="a">
        <fo:flow flow-name="xsl-region-body">
          <fo:block>Einkommensverteilung</fo:block>
          <fo:block>
            <fo:instream-foreign-object>
              <svg:svg 
                xmlns:svg="http://www.w3.org/2000/svg" 
                width="380" 
                height="{$vheight}" 
                style="fill:gray;">
                <svg:defs>
                  <svg:linearGradient 
                       id="wgrad" x1="0%" y1="0%" x2="100%" y2="0%">
                    <svg:stop offset="0%"   
                         style="stop-color:yellow;stop-opacity:1" />
                    <svg:stop offset="33%"  
                         style="stop-color:green;stop-opacity:1" />                    
                    <svg:stop offset="66%"  
                         style="stop-color:blue;stop-opacity:1" />
                    <svg:stop offset="100%" 
                         style="stop-color:black;stop-opacity:1" />
                  </svg:linearGradient>
                </svg:defs>                
                <svg:rect x="5" y="5" 
                  width="375" 
                  height="{$vheight - 5}" 
                  style="fill:none; stroke:black"/>
                <svg:text x="{$vAbstandVonLinks}" 
                          y="100">Normale Skalierung</svg:text> 
                <svg:rect x="{$vAbstandVonLinks}" 
                  y="{115}"
                  width="{7000 * 0.04}"
                  height="10"
                  style="fill:url(#wgrad)"/>
                <svg:rect x="{(100 * 0.04) + $vAbstandVonLinks}" 
                  y="{115}"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{(100 * 0.04) + $vAbstandVonLinks}" 
                  y="125" 
                  writing-mode="tb">100</svg:text>
                <svg:rect x="{(1000 * 0.04) + $vAbstandVonLinks}" 
                  y="{115}"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{(1000 * 0.04) + $vAbstandVonLinks}" 
                  y="125" 
                  writing-mode="tb">1.000</svg:text>
                <svg:rect x="{(5000 * 0.04) + $vAbstandVonLinks}" 
                  y="{115}"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{(5000 * 0.04) + $vAbstandVonLinks}" 
                  y="125" 
                  writing-mode="tb">5.000</svg:text>
                <svg:text x="{$vAbstandVonLinks}" 
                          y="260">Logarithmische Skalierung</svg:text>
                <svg:rect x="{$vAbstandVonLinks}" 
                  y="270"
                  width="{7000 * 0.04}"
                  height="10"
                  style="fill:url(#wgrad); stroke:black"/>
                <svg:rect x="{math:log(10) * 29 + $vAbstandVonLinks}" 
                  y="270"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{math:log(10) * 29 + $vAbstandVonLinks}" 
                  y="290" 
                  writing-mode="tb">10</svg:text>
                <svg:rect x="{math:log(100) * 29 + $vAbstandVonLinks}" 
                  y="270"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{math:log(100) * 29 + $vAbstandVonLinks}" 
                  y="290" 
                  writing-mode="tb">100</svg:text>
                <svg:rect x="{math:log(1000) * 29 + $vAbstandVonLinks}" 
                  y="270"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{math:log(1000) * 29 + $vAbstandVonLinks}" 
                  y="290" 
                  writing-mode="tb">1.000</svg:text>
                <svg:rect x="{math:log(10000) * 29 + $vAbstandVonLinks}" 
                  y="270"
                  width="1"
                  height="10"
                  style="fill:white"/>
                <svg:text 
                  x="{math:log(10000) * 29 + $vAbstandVonLinks}" 
                  y="290" 
                  writing-mode="tb">10.000</svg:text>
                <xsl:for-each select="/Orte/Ort/Mensch">
                  <xsl:sort 
                    select="Gehalt" 
                    data-type="number" 
                    order="ascending"/>
                  <svg:rect x="{(Gehalt * 0.04) + $vAbstandVonLinks}" 
                    y="{120}"
                    width="1"
                    height="1"
                    style="fill:red"/>
                  <svg:rect x="{math:log(Gehalt) * 29 + $vAbstandVonLinks}" 
                    y="{275}"
                    width="1"
                    height="1"
                    style="fill:red"/>
                </xsl:for-each> 
              </svg:svg>
            </fo:instream-foreign-object>
          </fo:block>          
        </fo:flow>
      </fo:page-sequence>
    </fo:root>
  </xsl:template>
</xsl:stylesheet>

pic/XSL_SVG_skalierung.png

wg / 28. April 2018



Fragen? Anmerkungen? Tipps?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/SVG.html