Wiederverwendbare SVG-Hilfswerkzeuge für Diagramme erzeugen

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten eine Bibliothek von SVG-Generatoren für Anwendungen erstellen, in denen es um die grafische Darstellung von Daten geht.

Lösung

XSLT 1.0

Wenn Sie vorhaben, sehr viel SVG-Code mit Hilfe von XSLT zu erzeugen, dann ist es hilfreich, eine Bibliothek von Templates zu erzeugen, um grafische Komponenten zu erstellen, die dann kombiniert werden können. Dieser Abschnitt zeigt einige dieser Komponenten zur Darstellung von Datenmaterial.

Erzeugen der Achsen

Dieses Beispiel erzeugt eine Menge von Templates zum Erstellen allgemeiner abgestufter X- und Y-Achsen:

<!-- Zeichne eine abgestufte X-Achse -->
<xsl:template name="svgu:xAxis">
  <xsl:param name="min" select="0"/> <!-- Min. X-Koordinate -->
  <xsl:param name="max" select="100"/> <!-- Max. X-Koordinate -->
  <xsl:param name="offsetX" select="0"/> <!-- X-Verschiebung der Achse -->
  <xsl:param name="offsetY" select="0"/> <!-- Y-Verschiebung der Achse -->
  <xsl:param name="width" select="500"/> <!-- Breite des physischen Plot-Bereichs -->
  <xsl:param name="height" select="500"/> <!-- Höhe des physischen Plot-Bereichs -->
  <xsl:param name="majorTicks" select="10"/> <!-- Anzahl der Hauptachseneinteilungen -->
  <xsl:param name="majorBottomExtent" select="4"/> <!-- Länge der Hauptteilstriche von der Achse nach unten -->
  <xsl:param name="majorTopExtent" select="$majorBottomExtent"/> <!-- Länge der Hauptteilstriche von der Achse nach oben -->
  <xsl:param name="labelMajor" select="true( )"/> <!-- Bezeichnet Hauptteilstriche, falls true -->
  <xsl:param name="minorTicks" select="4"/> <!-- Anzahl der Achsennebenteilstriche pro Hauptachsenteilsegment -->
  <xsl:param name="minorBottomExtent" select="2"/> <!-- Länge der Nebenteilstriche von der Achse nach unten -->
  <xsl:param name="minorTopExtent" select="$minorBottomExtent"/> <!-- Länge der Nebenteilstriche von der Achse nach oben -->
  <xsl:param name="context"/> <!-- Ein benutzerdefinierter Kontextbezeichner für die Formatierung von Template-Aufrufen -->
  <!-- Berechne Wertebereich und Skalierungsfaktoren -->
  <xsl:variable name="range" select="$max - $min"/>
  <xsl:variable name="scale" select="$width div $range"/>
  <!-- Erstelle ein kartesisches Koordinatensystem mit der richtigen Verschiebung und Skalierung -->
  <svg:g transform="translate({$offsetX},{$offsetY+$height}) scale({$scale},-1) translate({$min},0)">
    <!-- Zeichne eine Linie für die Achse -->
    <svg:line x1="{$min}" y1="0" x2="{$max}" y2="0">
      <xsl:attribute name="style">
        <!-- Rufe Template auf, das überschrieben werden kann, um den Stil der Achsen zu bestimmen. -->
        <xsl:call-template name="xAxisStyle">
          <xsl:with-param name="context" select="$context"/>
        </xsl:call-template>
      </xsl:attribute>
    </svg:line>
    <!-- Zeichne Teilstriche und ihre Bezeichnungen -->
    <xsl:call-template name="svgu:ticks">
      <xsl:with-param name="xMajor1" select="$min"/>
      <xsl:with-param name="yMajor1" select="$majorTopExtent"/>
      <xsl:with-param name="xMajor2" select="$min"/>
      <xsl:with-param name="yMajor2" select="-$majorBottomExtent"/>
      <xsl:with-param name="labelMajor" select="$labelMajor"/>
      <xsl:with-param name="freq" select="$minorTicks"/>
      <xsl:with-param name="xMinor1" select="$min"/>
      <xsl:with-param name="yMinor1" select="$minorTopExtent"/>
      <xsl:with-param name="xMinor2" select="$min"/>
      <xsl:with-param name="yMinor2" select="-$minorBottomExtent"/>
      <xsl:with-param name="nTicks" select="$majorTicks * $minorTicks + 1"/>
      <xsl:with-param name="xIncr" select="($max - $min) div ($majorTicks * $minorTicks)"/>
      <xsl:with-param name="scale" select="1 div $scale"/>
    </xsl:call-template>
  </svg:g>
</xsl:template>
<xsl:template name="svgu:yAxis">
  <xsl:param name="min" select="0"/> <!-- Min. X-Koordinate -->
  <xsl:param name="max" select="100"/> <!-- Max. X-Koordinate -->
  <xsl:param name="offsetX" select="0"/> <!-- X-Verschiebung der Achse -->
  <xsl:param name="offsetY" select="0"/> <!-- Y-Verschiebung der Achse -->
  <xsl:param name="width" select="500"/> <!-- Breite des physischen Plot-Bereichs -->
  <xsl:param name="height" select="500"/> <!-- Höhe des physischen Plot-Bereichs -->
  <xsl:param name="majorTicks" select="10"/> <!-- Anzahl der Hauptachseneinteilungen -->
  <xsl:param name="majorLeftExtent" select="4"/> <!-- Länge der Hauptteilstriche von der Achse nach links -->
  <xsl:param name="majorRightExtent" select="$majorBottomExtent"/> <!-- Länge der Hauptteilstriche von der Achse nach rechts -->
  <xsl:param name="labelMajor" select="true( )"/> <!-- Bezeichnet Hauptteilstriche, falls true -->
  <xsl:param name="minorTicks" select="4"/> <!-- Anzahl der Achsennebenteilstriche pro Hauptachsenteilsegment -->
  <xsl:param name="minorLeftExtent" select="2"/> <!-- Länge der Nebenteilstriche von der Achse nach links -->
  <xsl:param name="minorRightExtent" select="$minorBottomExtent"/> <!-- Länge der Nebenteilstriche von der Achse nach rechts -->
  <xsl:param name="context"/> <!-- Ein benutzerdefinierter Kontextbezeichner für die Formatierung von Template-Aufrufen -->
  <xsl:param name="majorLeftExtent" select="4"/>
  <xsl:param name="majorRightExtent" select="$majorLeftExtent"/>
  <xsl:param name="minorLeftExtent" select="2"/>
  <xsl:param name="minorRightExtent" select="$minorLeftExtent"/>
  <!-- Berechne Wertebereich und Skalierungsfaktoren -->
  <xsl:variable name="range" select="$max - $min"/>
  <xsl:variable name="scale" select="$height div $range"/>
  <!-- Erstelle ein kartesisches Koordinatensystem mit der richtigen Verschiebung und Skalierung  -->
  <svg:g transform="translate({$offsetX},{$offsetY+$height}) scale(1,{-$scale}) translate(0,{-$min})">
    <svg:line x1="0" y1="{$min}" x2="0" y2="{$max}">
      <xsl:attribute name="style">
        <xsl:call-template name="yAxisStyle">
          <xsl:with-param name="context" select="$context"/>
        </xsl:call-template>
      </xsl:attribute>
    </svg:line>
    <xsl:call-template name="svgu:ticks">
      <xsl:with-param name="xMajor1" select="-$majorLeftExtent"/>
      <xsl:with-param name="yMajor1" select="$min"/>
      <xsl:with-param name="xMajor2" select="$majorRightExtent"/>
      <xsl:with-param name="yMajor2" select="$min"/>
      <xsl:with-param name="labelMajor" select="$labelMajor"/>
      <xsl:with-param name="freq" select="$minorTicks"/>
      <xsl:with-param name="xMinor1" select="-$minorLeftExtent"/>
      <xsl:with-param name="yMinor1" select="$min"/>
      <xsl:with-param name="xMinor2" select="$minorRightExtent"/>
      <xsl:with-param name="yMinor2" select="$min"/>
      <xsl:with-param name="nTicks" select="$majorTicks * $minorTicks + 1"/>
      <xsl:with-param name="yIncr" select="($max - $min) div ($majorTicks * $minorTicks)"/>
      <xsl:with-param name="scale" select="1 div $scale"/>
    </xsl:call-template>
  </svg:g>
</xsl:template>
<!-- Rekursives Hilfsmittel zum Zeichnen von Teilstrichen und ihren Bezeichnungen -->
<xsl:template name="svgu:ticks">
  <xsl:param name="xMajor1" />
  <xsl:param name="yMajor1" />
  <xsl:param name="xMajor2" />
  <xsl:param name="yMajor2" />
  <xsl:param name="labelMajor"/>
  <xsl:param name="freq" />
  <xsl:param name="xMinor1" />
  <xsl:param name="yMinor1" />
  <xsl:param name="xMinor2" />
  <xsl:param name="yMinor2" />
  <xsl:param name="nTicks" select="0"/>
  <xsl:param name="xIncr" select="0"/>
  <xsl:param name="yIncr" select="0"/>
  <xsl:param name="i" select="0"/>
  <xsl:param name="scale"/>
  <xsl:param name="context"/>
  <xsl:if test="$i < $nTicks">
    <xsl:choose>
      <!-- Zeichne endlich einen Hauptteilstrich -->
      <xsl:when test="$i mod $freq = 0">
        <svg:line x1="{$xMajor1}" y1="{$yMajor1}" x2="{$xMajor2}" y2="{$yMajor2}"></svg:line>
        <xsl:if test="$labelMajor">
          <xsl:choose>

Dieser Codeteil erzeugt die Teilstriche entlang der X- und Y-Achsen. In diesem Beispiel ist der Formatierungsstring fest kodiert, um einen zusätzlichen Parameter zu vermeiden, aber möglicherweise möchten Sie einen solchen verwenden oder den Wert an ein weiteres Template für die Formatierung übergeben:

            <!-- Zeichne Teilstriche auf der X-Achse -->
            <xsl:when test="$xIncr &gt; 0">
              <!-- Teilstrich-Bezeichnung muss wegen des verzerrten Koordinatensystems korrigiert werden -->
              <svg:text x="{$xMajor1}" y="{$yMajor2}" transform="translate({$xMajor1},{$yMajor2}) scale({$scale},-1) translate({-$xMajor1},{-$yMajor2})">
                <xsl:attribute name="style">
                  <xsl:call-template name="xAxisLabelStyle">
                    <xsl:with-param name="context" select="$context"/>
                  </xsl:call-template>
                </xsl:attribute>
                <!-- Vielleicht sollte das Format der Bezeichnung ein Parameter sein -->
                <xsl:value-of select="format-number($xMajor1,'#0.0')"/>
              </svg:text>
            </xsl:when>
            <!-- Zeichne Teilstriche auf der Y-Achse -->
            <xsl:otherwise>
              <svg:text x="{$xMajor1}" y="{$yMajor1}" transform="translate({$xMajor1},{$yMajor1}) scale(1,{-$scale}) translate({-$xMajor1},{-$yMajor1})">
                <xsl:attribute name="style">
                  <xsl:call-template name="yAxisLabelStyle">
                    <xsl:with-param name="context" select="$context"/>
                  </xsl:call-template>
                </xsl:attribute>
                <xsl:value-of select="format-number($yMajor1,'#0.0')"/>
              </svg:text>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:if>
      </xsl:when>
      <!-- Zeichne endlich einen Nebenteilstrich -->
      <xsl:otherwise>
        <svg:line x1="{$xMinor1}" y1="{$yMinor1}" x2="{$xMinor2}" y2="{$yMinor2}"></svg:line>
      </xsl:otherwise>
    </xsl:choose>
    <!-- Rekursiver Aufruf für den nächsten Teilstrich -->
    <xsl:call-template name="svgu:ticks">
      <xsl:with-param name="xMajor1" select="$xMajor1 + $xIncr"/>
      <xsl:with-param name="yMajor1" select="$yMajor1 + $yIncr"/>
      <xsl:with-param name="xMajor2" select="$xMajor2 + $xIncr"/>
      <xsl:with-param name="yMajor2" select="$yMajor2 + $yIncr"/>
      <xsl:with-param name="labelMajor" select="$labelMajor"/>
      <xsl:with-param name="freq" select="$freq"/>
      <xsl:with-param name="xMinor1" select="$xMinor1 + $xIncr"/>
      <xsl:with-param name="yMinor1" select="$yMinor1 + $yIncr"/>
      <xsl:with-param name="xMinor2" select="$xMinor2 + $xIncr"/>
      <xsl:with-param name="yMinor2" select="$yMinor2 + $yIncr"/>
      <xsl:with-param name="nTicks" select="$nTicks"/>
      <xsl:with-param name="xIncr" select="$xIncr"/>
      <xsl:with-param name="yIncr" select="$yIncr"/>
      <xsl:with-param name="i" select="$i + 1"/>
      <xsl:with-param name="scale" select="$scale"/>
      <xsl:with-param name="context" select="$context"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>
<!-- Überschreibe dieses Template, um den Stil der X-Achse zu ändern -->
<xsl:template name="xAxisStyle">
  <xsl:param name="context"/>
  <xsl:text>stroke-width:0.5;stroke:black</xsl:text>
</xsl:template>
<!-- Überschreibe dieses Template, um den Stil der Y-Achse zu ändern -->
<xsl:template name="yAxisStyle">
  <xsl:param name="context"/>
  <xsl:text>stroke-width:0.5;stroke:black</xsl:text>
</xsl:template>
<!-- Überschreibe dieses Template, um den Stil der X-Achsenbezeichnung zu ändern -->
<xsl:template name="xAxisLabelStyle">
  <xsl:param name="context"/>
  <xsl:text>text-anchor:middle; font-size:8;baseline-shift:-110%</xsl:text>
</xsl:template>
<!-- Überschreibe dieses Template, um den Stil der Y-Achsenbezeichnung zu ändern -->
<xsl:template name="yAxisLabelStyle">
  <xsl:param name="context"/>
  <xsl:text>text-anchor:end;font-size:8;baseline-shift:-50%</xsl:text>
</xsl:template>

Dieses XSLT-Skript produziert X- und Y-Achsen mit großen und kleinen Teilstrichen und deren Beschriftungen:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">
  <xsl:import href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <xsl:variable name="width" select="300"/>
  <xsl:variable name="height" select="300"/>
  <xsl:variable name="pwidth" select="$width * 0.8"/>
  <xsl:variable name="pheight" select="$height * 0.8"/>
  <xsl:variable name="offsetX" select="($width - $pwidth) div 2"/>
  <xsl:variable name="offsetY" select="($height - $pheight) div 2"/>
  <xsl:template match="/">
    <svg:svg width="{$width}" height="{$height}">
      <xsl:call-template name="svgu:xAxis">
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="10"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:yAxis">
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="10"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
      </xsl:call-template>
    </svg:svg>
  </xsl:template>
</xsl:stylesheet>

Dieses Template erzeugt die Achsen, die in der folgenden Abbildung dargestellt sind.

Wiederverwendbare SVG-Achsen

Abbildung: Wiederverwendbare SVG-Achsen.

Durch die Ausdehnung der großen Teilstriche auf die volle Breite und Höhe können Sie ein Gitter erzeugen, wie es in der folgenden Abbildung zu sehen ist:

Ein wiederverwendbares SVG-Gitter

Abbildung: Ein wiederverwendbares SVG-Gitter.

<xsl:call-template name="svgu:xAxis">
  <xsl:with-param name="min" select="0"/>
  <xsl:with-param name="max" select="10"/>
  <xsl:with-param name="offsetX" select="$offsetX"/>
  <xsl:with-param name="offsetY" select="$offsetY"/>
  <xsl:with-param name="width" select="$pwidth"/>
  <xsl:with-param name="height" select="$pheight"/>
  <xsl:with-param name="majorTopExtent" select="$pheight"/>
</xsl:call-template>
<xsl:call-template name="svgu:yAxis">
  <xsl:with-param name="min" select="0"/>
  <xsl:with-param name="max" select="10"/>
  <xsl:with-param name="offsetX" select="$offsetX"/>
  <xsl:with-param name="offsetY" select="$offsetY"/>
  <xsl:with-param name="width" select="$pwidth"/>
  <xsl:with-param name="height" select="$pheight"/>
  <xsl:with-param name="majorRightExtent" select="$pwidth"/>
</xsl:call-template>

Wenn Sie auch die kleinen Teilstriche auf die gesamte Breite und Höhe ausdehnen, erhalten Sie ein feineres Gitter, wie Sie es in der folgenden Abbildung sehen können.

Ein feineres wiederverwendbares Gitter

Abbildung: Ein feineres wiederverwendbares Gitter.

<xsl:call-template name="svgu:xAxis">
  <xsl:with-param name="min" select="0"/>
  <xsl:with-param name="max" select="10"/>
  <xsl:with-param name="offsetX" select="$offsetX"/>
  <xsl:with-param name="offsetY" select="$offsetY"/>
  <xsl:with-param name="width" select="$pwidth"/>
  <xsl:with-param name="height" select="$pheight"/>
  <xsl:with-param name="majorTopExtent" select="$pheight"/>
  <xsl:with-param name="minorTopExtent" select="$pheight"/>
</xsl:call-template>
<xsl:call-template name="svgu:yAxis">
  <xsl:with-param name="min" select="0"/>
  <xsl:with-param name="max" select="10"/>
  <xsl:with-param name="offsetX" select="$offsetX"/>
  <xsl:with-param name="offsetY" select="$offsetY"/>
  <xsl:with-param name="width" select="$pwidth"/>
  <xsl:with-param name="height" select="$pheight"/>
  <xsl:with-param name="majorRightExtent" select="$pwidth"/>
  <xsl:with-param name="minorRightExtent" select="$pwidth"/>
</xsl:call-template>

Einen Plot mit vier Quadranten wie in der folgenden Abbildung können Sie dadurch erstellen, dass Sie die Achsen verschieben und die Ausdehnung des Gitters anpassen:

Ein wiederverwendbares Gitter für vier Quadranten

Abbildung: Ein wiederverwendbares Gitter für vier Quadranten.

<xsl:call-template name="svgu:xAxis">
  <xsl:with-param name="min" select="-5"/>
  <xsl:with-param name="max" select="5"/>
  <xsl:with-param name="offsetX" select="0"/>
  <xsl:with-param name="offsetY" select="-$pheight div 2"/>
  <xsl:with-param name="width" select="$pwidth"/>
  <xsl:with-param name="height" select="$pheight"/>
  <xsl:with-param name="majorTopExtent" select="$pwidth div 2"/>
  <xsl:with-param name="majorBottomExtent" select="$pwidth div 2"/>
</xsl:call-template>
<xsl:call-template name="svgu:yAxis">
  <xsl:with-param name="min" select="-5"/>
  <xsl:with-param name="max" select="5"/>
  <xsl:with-param name="offsetX" select="-$pwidth div 2"/>
  <xsl:with-param name="offsetY" select="0"/>
  <xsl:with-param name="width" select="$pwidth"/>
  <xsl:with-param name="height" select="$pheight"/>
  <xsl:with-param name="majorRightExtent" select="$pwidth div 2"/>
  <xsl:with-param name="majorLeftExtent" select="$pwidth div 2"/>
</xsl:call-template>

Die Beschriftungen werden bei diesem Code standardmäßig an den Enden des Gitters ausgerichtet. Allerdings können Sie diese in die Nähe der Achsen zurückbringen, indem Sie zwei Templates überschreiben, wodurch die in der folgenden Abbildung dargestellten Ergebnisse entstehen.

Ein wiederverwendbares Vier-Quadranten-Gitter mit achsennahen Beschriftungen

Abbildung: Ein wiederverwendbares Vier-Quadranten-Gitter mit achsennahen Beschriftungen.

<xsl:template name="xAxisLabelYOffset">
  <xsl:value-of select="-$pheight div 2"/>
</xsl:template>
<xsl:template name="yAxisLabelXOffset">
  <xsl:value-of select="$pwidth div 2"/>
</xsl:template>

Erzeugen der Balken

Eine weitere häufig vorkommende Darstellungsart bei Daten-Plots sind Balkendiagramme, die zum Vergleichen von Daten sehr praktisch sind. Erstellen Sie nun ein Hilfsmittel zur Produktion eines Balkens für alle an ihn übergebenen Datenwerte. Die Breite und Farbe der Balken kann leicht verändert werden. Beim nächsten Beispiel können die Balken in verschiedenen Richtungen erstellt werden, indem eine Rotationstransformation auf das Koordinatensystem angewendet wird. Diese Eigenschaft ist zwar sehr nützlich, aber Sie müssen nun die Folgen dieser Rotation auf den Text und die Reihenfolge der Datenwerte kompensieren:

<xsl:template name="svgu:bars">
  <xsl:param name="data" select="/.."/> <!-- darzustellende Daten -->
  <xsl:param name="width" select="500"/>
  <xsl:param name="height" select="500"/>
  <xsl:param name="orientation" select="0"/>
  <xsl:param name="barWidth" select="5"/>
  <xsl:param name="offsetX" select="0"/>
  <xsl:param name="offsetY" select="0"/>
  <xsl:param name="boundingBox" select="false( )"/>
  <xsl:param name="barLabel" select="false( )"/>
  <xsl:param name="max">
    <xsl:call-template name="emath:max">
      <xsl:with-param name="nodes" select="$data"/>
    </xsl:call-template>
  </xsl:param>
  <xsl:param name="context"/>
  <xsl:variable name="numBars" select="count($data)"/>
  <xsl:variable name="spacing" select="$width div ($numBars + 1)"/>
  <xsl:if test="$boundingBox">
    <svg:g transform="translate({$offsetX},{$offsetY}) translate({$width div 2},{$height div 2}) rotate({$orientation - 180}) translate({-$width div 2},{-$height div 2})">
      <svg:rect x="0" y="0" height="{$height}" width="{$width}" style="stroke: black; stroke-width: 0.5; stroke-opacity:0.5; fill:none"/>
    </svg:g>
  </xsl:if>
  <!-- Wir ändern die Datenreihenfolge, um die Drehung auszugleichen. Siehe Sortierung unten. -->
  <xsl:variable name="data-order">
    <xsl:choose>
      <xsl:when test="$orientation mod 360 &gt;= 180">ascending</xsl:when>
      <xsl:otherwise>descending</xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <svg:g transform="translate({$offsetX},{$offsetY}) translate({$width div 2},{$height div 2}) rotate({$orientation - 180}) translate({-$width div 2},{-$height div 2}) scale(1,{$height div $max})">
    <xsl:for-each select="$data">
      <!-- Wir sortieren nach Position, um rückwärts über die Daten zu traversieren, falls nötig. -->
      <xsl:sort select="position( )" data-type="number" order="{$data-order}"/>
      <xsl:variable name="pos" select="position( )"/>

In diesem Beispiel bestehen die Balken aus Linien, deren Farbe und Linienbreite durch Überschreiben des BarStyle-Templates verändert werden kann. Möglicherweise werden Sie lieber Rechtecke verwenden, bei denen Sie auch den Randstil verändern können:

      <svg:line x1="{$spacing * $pos}" y1="0" x2="{$spacing * $pos}" y2="{current( )}" id="{$context}_bar_{$pos}">
        <xsl:attribute name="style">
          <xsl:value-of select="concat('stroke-width: ',$barWidth,'; ')"/>
          <xsl:call-template name="svgu:barStyle">
            <xsl:with-param name="pos" select="$pos"/>
            <xsl:with-param name="context" select="$context"/>
          </xsl:call-template>
        </xsl:attribute>
      </svg:line>
      <!-- Falls ein Benutzer Balkenbezeichnungen wünscht, dann positionieren wir den Datenwert als Text über den Balken. Diese komplexe Transformationsfolge wird benutzt, damit der Text korrekt angezeigt wird, obwohl das Koordinatensystem gedreht und skaliert wird. -->
      <xsl:if test="$barLabel">
        <svg:text x="{$spacing * $pos}" y="{current( ) * ($height div $max)}" transform="scale(1,{$max div $height}) translate(0,10) translate({$spacing * $pos},{current( ) * ($height div $max)}) rotate({180 - $orientation}) translate({-$spacing * $pos},{-current( ) * ($height div $max)})" id="{$context}_barLabel_{$pos}">
          <xsl:attribute name="style">
            <xsl:call-template name="svgu:barLabelStyle">
              <xsl:with-param name="pos" select="$pos"/>
              <xsl:with-param name="context" select="$context"/>
            </xsl:call-template>
          </xsl:attribute>
          <xsl:value-of select="."/>
        </svg:text>
      </xsl:if>
    </xsl:for-each>
  </svg:g>
</xsl:template>
<xsl:template name="svgu:barStyle">
  <xsl:param name="pos"/>
  <xsl:param name="context"/>
  <xsl:variable name="colors" select="document('')/*/svgu:color"/>
  <xsl:value-of select="concat('stroke: ',$colors[($pos - 1 ) mod count($colors)+ 1])"/>
</xsl:template>
<xsl:template name="svgu:barLabelStyle">
  <xsl:param name="pos"/>
  <xsl:param name="context"/>
  <xsl:value-of select=" 'text-anchor: middle' "/>
</xsl:template>

Das folgende Stylesheet zeigt Daten mit Hilfe von Balken an. Das Ergebnis sehen Sie in der folgenden Abbildung.

Ein generiertes Balkendiagramm

Abbildung: Ein generiertes Balkendiagramm.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu">
  <xsl:import href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <test:data>1.0</test:data>
  <test:data>2.0</test:data>
  <test:data>3.0</test:data>
  <test:data>4.0</test:data>
  <test:data>5.0</test:data>
  <test:data>13.0</test:data>
  <test:data>2.7</test:data>
  <test:data>13.9</test:data>
  <test:data>22.0</test:data>
  <test:data>8.5</test:data>
  <xsl:template match="/">
    <svg:svg width="400" height="400">
      <xsl:call-template name="svgu:bars">
        <xsl:with-param name="data" select="document('')/*/test:data"/>
        <xsl:with-param name="width" select=" '300' "/>
        <xsl:with-param name="height" select=" '350' "/>
        <xsl:with-param name="orientation" select=" '0' "/>
        <xsl:with-param name="offsetX" select=" '50' "/>
        <xsl:with-param name="offsetY" select=" '25' "/>
        <xsl:with-param name="boundingBox" select="1"/>
        <xsl:with-param name="barLabel" select="1"/>
        <xsl:with-param name="max" select="25"/>
      </xsl:call-template>
    </svg:svg>
  </xsl:template>
  <xsl:template name="svgu:barLabelStyle">
    <xsl:param name="pos"/>
    <xsl:param name="context"/>
    <xsl:text>text-anchor: middle; font-size: 8</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Diese Variante dreht die Ausgabe, um ein horizontales Balkendiagramm zu erstellen, wie es in der folgenden Abbildung zu sehen ist. Der Code versucht nicht, den Richtungswinkel einzuschränken, auch wenn nur ein Vielfaches von 90 Grad dafür vorkommen dürfte. (Anmerkung: Wenn Sie Ihren krankhaft ordnungsliebenden Chef ärgern wollen, können Sie ein Balkendiagramm mit einem Winkel von 72 Grad erstellen. Noch besser: Verwenden Sie einen Winkel von einem Grad, und überreden Sie Ihre Kollegen zu beschwören, dass das Diagramm vollkommen gerade aussieht!)

Ein gedrehtes Balkendiagramm

Abbildung: Ein gedrehtes Balkendiagramm.

<xsl:call-template name="svgu:bars">
  <xsl:with-param name="data" select="document('')/*/test:data"/>
  <xsl:with-param name="width" select=" '300' "/>
  <xsl:with-param name="height" select=" '350' "/>
  <xsl:with-param name="orientation" select=" '90' "/>
  <xsl:with-param name="offsetX" select=" '50' "/>
  <xsl:with-param name="offsetY" select=" '25' "/>
  <xsl:with-param name="boundingBox" select="1"/>
  <xsl:with-param name="barLabel" select="1"/>
  <xsl:with-param name="max" select="25"/>
</xsl:call-template>

XY-Plots

Achsen und Gitter sind erst dann von Nutzen, wenn Sie auch die eigentlichen Daten dazwischen plotten können. Eine häufige Darstellung besteht aus einem XY-Plot, in dem ein Wert als Funktion eines anderen erscheint. Dafür können Sie ein Hilfsmittel erstellen, das jeweils einen Datensatz bearbeitet, um es mehrmals so zu benutzen, dass Sie verschiedene Datensätze im gleichen Diagramm anzeigen können:

<xsl:template name="svgu:xyPlot">
  <xsl:param name="dataX" select="/.."/> <!-- X-Werte -->
  <xsl:param name="dataY" select="/.."/>
  <xsl:param name="offsetX" select="0"/>
  <xsl:param name="offsetY" select="0"/>
  <xsl:param name="width" select="500"/>
  <xsl:param name="height" select="500"/>
  <xsl:param name="boundingBox" select="false( )"/>
  <xsl:param name="context"/>
  <xsl:param name="maxX">
    <xsl:call-template name="emath:max">
      <xsl:with-param name="nodes" select="$dataX"/>
    </xsl:call-template>
  </xsl:param>
  <xsl:param name="maxY">
    <xsl:call-template name="emath:max">
      <xsl:with-param name="nodes" select="$dataY"/>
    </xsl:call-template>
  </xsl:param>
  <xsl:variable name="scaleX" select="$width div $maxX"/>
  <xsl:variable name="scaleY" select="$height div $maxY"/>

Um es einfacher zu machen, verwendet dieser Abschnitt eine Java-Erweiterungsfunktion, aber Sie könnten max auch in XPath mit select="($scaleX &gt; $scaleY) * $scaleX + not($scaleX &gt; $scaleY) * $scaleY)" implementieren:

<xsl:variable name="scale" select="Math:max($scaleX,$scaleY)"/>
<xsl:if test="$boundingBox">
  <svg:g transform="translate({$offsetX},{$offsetY})">
    <svg:rect x="0" y="0" height="{$height}" width="{$width}" style="stroke: black; stroke-width: 0.5; stroke-opacity: 0.5; fill: none"/>
  </svg:g>
</xsl:if>

Hierbei zeichne ich die Kurve mit einfachen Linienabschnitten, aber wenn Sie einen Befehl für Bézier-Kurven verwenden, erhalten Sie wahrscheinlich weichere Übergänge, wobei dann aber auch der Code komplizierter wird. Da es in diesem Buch aber mehr um XSLT als um SVG geht, steht hier das Einfache im Vordergrund. Der Trick, den man bei kubischen Bézier-Kurven braucht, besteht vielleicht darin, jeweils drei Punkte zu plotten, von denen der mittlere als Kontrollpunkt verwendet wird, aber ich habe das nicht getestet:

  <svg:path transform="translate({$offsetX},{$height + $offsetY}) scale({$scaleX},{-$scaleY})">
    <xsl:attribute name="d">
      <xsl:for-each select="$dataX">
        <xsl:variable name="pos" select="position( )"/>
        <xsl:variable name="x" select="current( ) "/>
        <xsl:variable name="y" select="$dataY[$pos]"/>
        <xsl:choose>
          <xsl:when test="$pos = 1">
            <xsl:text>M </xsl:text>
          </xsl:when>
          <xsl:otherwise> L </xsl:otherwise>
        </xsl:choose>
        <xsl:value-of select="$x"/>,<xsl:value-of select="$y"/>
      </xsl:for-each>
    </xsl:attribute>
    <xsl:attribute name="style">
      <xsl:call-template name="svgu:xyPlotStyle">
        <xsl:with-param name="scale" select="$scale"/>
        <xsl:with-param name="context" select="$context"/>
      </xsl:call-template>
    </xsl:attribute>
  </svg:path>
</xsl:template>
<xsl:template name="svgu:xyPlotStyle">
  <xsl:param name="context"/>
  <xsl:param name="scale"/>
  <xsl:value-of select="concat('fill: none; stroke: black; stroke-width:',1 div $scale,'; ')"/>
</xsl:template>

Dieses XSLT-Skript verwendet das XY-Plot-Template, mit dem es das Ergebnis in der folgenden Abbildung produziert. Um es einfach zu halten, habe ich hier die Daten in das eigentliche Stylesheet aufgenommen. In der Praxis würden Sie die Daten natürlich aus einem anderen XML-Dokument holen:

Plotten mit SVG und XSLT

Abbildung: Plotten mit SVG und XSLT.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">
  <xsl:import href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <test:xdata>0</test:xdata>
  <test:xdata>5</test:xdata>
  <test:xdata>10</test:xdata>
  <test:xdata>15</test:xdata>
  <test:xdata>20</test:xdata>
  <test:xdata>25</test:xdata>
  <test:xdata>30</test:xdata>
  <!-- Restliche X-Daten ausgelassen ... -->
  <test:ydata>0</test:ydata>
  <test:ydata>0.087155743</test:ydata>
  <test:ydata>0.173648178</test:ydata>
  <test:ydata>0.258819045</test:ydata>
  <test:ydata>0.342020143</test:ydata>
  <test:ydata>0.422618262</test:ydata>
  <test:ydata>0.5</test:ydata>
  <!-- Restliche Y-Daten ausgelassen ... -->
  <xsl:variable name="w" select="400"/>
  <xsl:variable name="h" select="300"/>
  <xsl:variable name="pwidth" select="$w * 0.8"/>
  <xsl:variable name="pheight" select="$h * 0.8"/>
  <xsl:variable name="offsetX" select="($w - $pwidth) div 2"/>
  <xsl:variable name="offsetY" select="($h - $pheight) div 2"/>
  <xsl:template match="/">
    <svg:svg width="{$w}" height="{$h}">
      <xsl:call-template name="svgu:xyPlot">
        <xsl:with-param name="dataX" select="document('')/*/test:xdata"/>
        <xsl:with-param name="dataY" select="document('')/*/test:ydata"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <!-- <xsl:with-param name="minY" select="-1"/>
             <xsl:with-param name="maxY" select="1"/>
        -->
      </xsl:call-template>
      <xsl:call-template name="svgu:xAxis">
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="360"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="-$pheight div 2 + $offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="majorTicks" select="6"/>
        <!-- Anzahl der Achsenhauptunterteilungen -->
        <xsl:with-param name="minorTicks" select="4"/>
        <!-- Anzahl der Achsenhauptunterteilungen -->
     </xsl:call-template>
     <xsl:call-template name="svgu:yAxis">
       <xsl:with-param name="min" select="-1"/>
       <xsl:with-param name="max" select="1"/>
       <xsl:with-param name="offsetX" select="$offsetX"/>
       <xsl:with-param name="offsetY" select="$offsetY"/>
       <xsl:with-param name="width" select="$pwidth"/>
       <xsl:with-param name="height" select="$pheight"/>
     </xsl:call-template>
   </svg:svg>
  </xsl:template>
</xsl:stylesheet>

Der folgende Code, dessen Ausgabe Sie in der folgenden Abbildung sehen können, demonstriert, wie man mehrere Datensätze ausgeben und den Linienstil durch Überschreiben eines Templates anpassen kann:

Mehrere mit XSLT erzeugte Plots

Abbildung: Mehrere mit XSLT erzeugte Plots.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">
  <xsl:import href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <!-- Datenwerte ausgelassen ... -->
  <xsl:variable name="w" select="400"/>
  <xsl:variable name="h" select="300"/>
  <xsl:variable name="pwidth" select="$w * 0.8"/>
  <xsl:variable name="pheight" select="$h * 0.8"/>
  <xsl:variable name="offsetX" select="($w - $pwidth) div 2"/>
  <xsl:variable name="offsetY" select="($h - $pheight) div 2"/>
  <xsl:template match="/">
    <svg:svg width="{$w}" height="{$h}">
      <xsl:call-template name="svgu:xyPlot">
        <xsl:with-param name="dataX" select="document('')/*/test:xdata"/>
        <xsl:with-param name="dataY" select="document('')/*/test:ydata"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="maxY" select="40"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:xyPlot">
        <xsl:with-param name="dataX" select="document('')/*/test:xdata"/>
        <xsl:with-param name="dataY" select="document('')/*/test:y2data"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="maxY" select="40"/>
        <xsl:with-param name="context" select="2"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:xAxis">
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="6"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="majorTopExtent" select="$pheight"/>
        <xsl:with-param name="minorTopExtent" select="$pheight"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:yAxis">
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="40"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="majorRightExtent" select="$pwidth"/>
        <xsl:with-param name="minorRightExtent" select="$pwidth"/>
      </xsl:call-template>
    </svg:svg>
  </xsl:template>
  <!-- Ein angepasster Stil benutzt den Kontext, um herauszufinden, welche Linie gerade gezeichnet wird. -->
  <xsl:template name="svgu:xyPlotStyle">
    <xsl:param name="context"/>
    <xsl:param name="scale"/>
    <xsl:choose>
      <xsl:when test="$context = 2">
        <xsl:value-of select="concat('fill: none; stroke: red; stroke-width:',16 div $scale,'; ')"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat('fill: none; stroke: black; stroke-width:',1 div $scale,'; ')"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Erzeugen von Tortendiagrammen

Tortendiagramme sind eine weitere beliebte Art, um Daten zu vergleichen. Auch für die Erstellung solcher Diagramme können Sie ein Hilfskonstrukt erstellen. Das Wichtigste bei Tortendiagrammen ist die Möglichkeit, einzelne Tortenstücke zu erstellen, wobei man es unausweichlich mit Trigonometrie zu tun bekommt. Da es in XSLT keine trigonometrischen Funktionen gibt, werden Sie hierfür eine Java-basierte Erweiterung verwenden. Natürlich wird dadurch die Portabilität Ihres Stylesheets sofort eingeschränkt. Falls diese Portabilität ein Muss ist, könnten Sie die Sinus- und Cosinus-Funktionen auch in XSLT implementieren (siehe das Rezept Gebräuchliche mathematische Funtionen implementieren als Orientierungshilfe). Ansonsten können Sie den folgenden Code in Stylesheets einsetzen, die mathematische Java-Erweiterungen benötigen. Die Details hierbei variieren von Prozessor zu Prozessor. Weitere Angaben dazu finden Sie unter Code-Generierung. Das folgende Beispiel funktioniert mit Saxon:

<!-- version 1.1 ist erloschen, aktiviert aber in Saxon die Eigenschaft xsl:script. -->
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:emath="http://www.exslt.org/math" xmlns:Math="java:java.lang.Math" extension-element-prefixes="Math" exclude-result-prefixes="svgu">
  <xsl:script implements-prefix="Math" xmlns:Math="java:java.lang.Math" language="java" src="java:java.lang.Math"/>
  <!-- Wir nutzen einige der im Kapitel "Strings" entwickelten XSLT-Sachen. -->
  <xsl:include href="../math/math.max.xslt"/>
  <xsl:include href="../math/math.min.xslt"/>
  ...
</xsl:stylesheet>

Den Großteil der mathematischen Arbeit übernimmt eine Routine namens svgu:pieSlice. Sie ist eine Abwandlung einer Routine in einem Perl-Programm aus dem Buch SVG Essentials von J. David Eisenberg (O'Reilly, 2002). Den trigonometrischen Hintergrund zu erklären, würde den Rahmen dieses Buches sprengen, nur so viel: Mit dieser Routine können Sie Bögen zeichnen (die auf Drehungen um ein Drehzentrum herum basieren) und können somit die weniger intuitiven Bögen der SVG-Spezifikation vermeiden:

<xsl:variable name="svgu:pi" select="3.1415927"/>
<xsl:template name="svgu:pieSlice">
  <xsl:param name="cx" select="100"/> <!-- Zentrum, X -->
  <xsl:param name="cy" select="100"/> <!-- Zentrum, Y -->
  <xsl:param name="r" select="50"/> <!-- Radius -->
  <xsl:param name="theta" select="0"/> <!-- Startwinkel in Grad -->
  <xsl:param name="delta" select="90"/> <!-- Bogenumfang in Grad -->
  <xsl:param name="phi" select="0"/>  <!-- X-Achsendrehwinkel -->
  <xsl:param name="style" select=" 'fill: red;' "/>
  <xsl:param name="num"/>
  <xsl:param name="context"/>
  <!-- Konvertiere Winkel in Bogenmaß (Radian) um -->
  <xsl:variable name="theta1" select="$theta * $svgu:pi div 180"/>
  <xsl:variable name="theta2" select="($delta + $theta) * $svgu:pi div 180"/>
  <xsl:variable name="phi_r" select="$phi * $svgu:pi div 180"/>
  <!-- Finde Anfangs- und Endkoordinaten heraus -->
  <xsl:variable name="x0" select="$cx + Math:cos($phi_r) * $r * Math:cos($theta1) + Math:sin(-$phi_r) * $r * Math:sin($theta1)"/>
  <xsl:variable name="y0" select="$cy + Math:sin($phi_r) * $r * Math:cos($theta1) + Math:cos($phi_r) * $r * Math:sin($theta1)"/>
  <xsl:variable name="x1" select="$cx + Math:cos($phi_r) * $r * Math:cos($theta2) + Math:sin(-$phi_r) * $r * Math:sin($theta2)"/>
  <xsl:variable name="y1" select="$cy + Math:sin($phi_r) * $r * Math:cos($theta2) + Math:cos($phi_r) * $r * Math:sin($theta2)"/>
  <xsl:variable name="large-arc" select="($delta &gt; 180) * 1"/>
  <xsl:variable name="sweep" select="($delta &gt; 0) * 1"/>
  <svg:path style="{$style}" id="{$context}_pieSlice_{$num}">
    <xsl:attribute name="d">
      <xsl:value-of select="concat('M ', $x0,' ',$y0,' A ', $r,' ',$r,',', $phi,',', $large-arc,',', $sweep,',', $x1,' ',$y1,' L ',$cx,' ',$cy,' L ', $x0,' ',$y0)"/>
    </xsl:attribute>
  </svg:path>
</xsl:template>
<xsl:template name="svgu:pieSliceLabel">
  <xsl:param name="label" /> <!-- Bezeichnung -->
  <xsl:param name="cx" select="100"/> <!-- Zentrum, X -->
  <xsl:param name="cy" select="100"/> <!-- Zentrum, Y -->
  <xsl:param name="r" select="50"/> <!-- Radius -->
  <xsl:param name="theta" select="0"/> <!-- Startwinkel in Grad -->
  <xsl:param name="delta" select="90"/> <!-- Bogenumfang in Grad -->
  <xsl:param name="style" select=" 'font-size: 18;' "/>
  <xsl:param name="num"/>
  <xsl:param name="context"/>
  <!-- Konvertiere Winkel in Bogenmaß (Radian) um -->
  <xsl:variable name="theta2" select="(($delta + $theta) mod 360 + 360) mod 360"/>
  <!-- Normalisiere Winkel -->
  <xsl:variable name="theta2_r" select="$theta2 * $svgu:pi div 180"/>
  <xsl:variable name="x" select="$cx + $r * Math:cos($theta2_r)"/>
  <xsl:variable name="y" select="$cy + $r * Math:sin($theta2_r)"/>
  <!-- Berechne Ankerpunkt für positionsabhängigen Text um die Torte herum. Erzeugt recht gleichmäßige Abstände. -->
  <xsl:variable name="anchor">
    <xsl:choose>
      <xsl:when test="contains($style,'text-anchor')"></xsl:when>
      <xsl:when test="$theta2 &gt;= 0 and $theta2 &lt;= 45">start</xsl:when>
      <xsl:when test="$theta2 &gt; 45 and $theta2 &lt;= 135">middle</xsl:when>
      <xsl:when test="$theta2 &gt; 135 and $theta2 &lt;= 225">end</xsl:when>
      <xsl:when test="$theta2 &gt; 225 and $theta2 &lt;= 315">middle</xsl:when>
      <xsl:otherwise>start</xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <svg:text x="{$x}" y="{$y}" style="text-anchor:{$anchor};{$style}" id="{$context}_pieSliceLabel_{$num}">
    <xsl:value-of select="$label"/>
  </svg:text>
</xsl:template>
<xsl:template name="svgu:pie">
  <xsl:param name="data" select="/.."/> <!-- Darzustellende Daten -->
  <xsl:param name="cx" select="100"/> <!-- Zentrum, X -->
  <xsl:param name="cy" select="100"/> <!-- Zentrum, Y -->
  <xsl:param name="r" select="50"/> <!-- Radius -->
  <xsl:param name="theta" select="-90"/> <!-- Startwinkel für erstes Stück in Grad -->
  <xsl:param name="context"/> <!-- Benutzerdaten zur Identifikation dieses Aufrufs -->
  <xsl:call-template name="svgu:pieImpl">
    <xsl:with-param name="data" select="$data"/>
    <xsl:with-param name="cx" select="$cx"/>
    <xsl:with-param name="cy" select="$cy"/>
    <xsl:with-param name="r" select="$r"/>
    <xsl:with-param name="theta" select="$theta"/>
    <xsl:with-param name="sum" select="sum($data)"/>
    <xsl:with-param name="context" select="$context"/>
  </xsl:call-template>
</xsl:template>
<!-- Rekursive Implementierung -->
<xsl:template name="svgu:pieImpl">
  <xsl:param name="data" />
  <xsl:param name="cx" />
  <xsl:param name="cy" />
  <xsl:param name="r" />
  <xsl:param name="theta"/>
  <xsl:param name="sum"/>
  <xsl:param name="context"/>
  <xsl:param name="i" select="1"/>
  <xsl:if test="count($data) &gt;= $i">
    <xsl:variable name="delta" select="($data[$i] * 360) div $sum"/>
    <!-- Zeichne ein Tortenstück -->
    <xsl:call-template name="svgu:pieSlice">
      <xsl:with-param name="cx" select="$cx"/>
      <xsl:with-param name="cy" select="$cy"/>
      <xsl:with-param name="r" select="$r"/>
      <xsl:with-param name="theta" select="$theta"/>
      <xsl:with-param name="delta" select="$delta"/>
      <xsl:with-param name="style">
        <xsl:call-template name="svgu:pieSliceStyle">
          <xsl:with-param name="i" select="$i"/>
          <xsl:with-param name="context" select="$context"/>
        </xsl:call-template>
      </xsl:with-param>
      <xsl:with-param name="num" select="$i"/>
      <xsl:with-param name="context" select="$context"/>
    </xsl:call-template>
    <!-- Rekursiver Aufruf für nächstes Tortenstück -->
    <xsl:call-template name="svgu:pieImpl">
      <xsl:with-param name="data" select="$data"/>
      <xsl:with-param name="cx" select="$cx"/>
      <xsl:with-param name="cy" select="$cy"/>
      <xsl:with-param name="r" select="$r"/>
      <xsl:with-param name="theta" select="$theta + $delta"/>
      <xsl:with-param name="sum" select="$sum"/>
      <xsl:with-param name="context" select="$context"/>
      <xsl:with-param name="i" select="$i + 1"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>
<!-- Setzt die Bezeichnungen aller Tortenstücke um die Grafik herum -->
<xsl:template name="svgu:pieLabels">
  <xsl:param name="data" select="/.."/> <!-- Daten der Stücke -->
  <xsl:param name="labels" select="$data"/> <!-- Knotenmenge mit darzustellenden Bezeichnungen, voreingestellt auf data -->
  <xsl:param name="cx" select="100"/> <!-- Zentrum, X>
  <xsl:param name="cy" select="100"/> <!-- Zentrum, y -->
  <xsl:param name="r" select="50"/> <!-- Radius -->
  <xsl:param name="theta" select="-90"/> <!-- Startwinkel für erstes Stück in Grad -->
  <xsl:param name="context"/> <!-- Benutzerdaten zur Identifikation dieses Aufrufs -->
  <xsl:call-template name="svgu:pieLabelsImpl">
    <xsl:with-param name="data" select="$data"/>
    <xsl:with-param name="labels" select="$labels"/>
    <xsl:with-param name="cx" select="$cx"/>
    <xsl:with-param name="cy" select="$cy"/>
    <xsl:with-param name="r" select="$r"/>
    <xsl:with-param name="theta" select="$theta"/>
    <xsl:with-param name="sum" select="sum($data)"/>
    <xsl:with-param name="context" select="$context"/>
  </xsl:call-template>
</xsl:template>
<xsl:template name="svgu:pieLabelsImpl">
  <xsl:param name="data" />
  <xsl:param name="labels"/>
  <xsl:param name="cx" />
  <xsl:param name="cy" />
  <xsl:param name="r" />
  <xsl:param name="theta"/>
  <xsl:param name="sum"/>
  <xsl:param name="context"/>
  <xsl:param name="i" select="1"/>
  <xsl:if test="count($data) &gt;= $i">
    <xsl:variable name="delta" select="($data[$i] * 360) div $sum"/>
    <!-- Zeichne Tortenstück -->
    <xsl:call-template name="svgu:pieSliceLabel">
      <xsl:with-param name="label" select="$labels[$i]"/>
      <xsl:with-param name="cx" select="$cx"/>
      <xsl:with-param name="cy" select="$cy"/>
      <xsl:with-param name="r" select="$r"/>
      <xsl:with-param name="theta" select="$theta"/>
      <xsl:with-param name="delta" select="$delta div 2"/>
      <xsl:with-param name="style">
        <xsl:call-template name="svgu:pieSliceLabelStyle">
          <xsl:with-param name="i" select="$i"/>
          <xsl:with-param name="value" select="$data[$i]"/>
          <xsl:with-param name="label" select="$labels[$i]"/>
          <xsl:with-param name="context" select="$context"/>
        </xsl:call-template>
      </xsl:with-param>
      <xsl:with-param name="num" select="$i"/>
      <xsl:with-param name="context" select="$context"/>
    </xsl:call-template>
    <!-- Rekursiver Aufruf für nächste Tortenstückbezeichnung -->
    <xsl:call-template name="svgu:pieLabelsImpl">
      <xsl:with-param name="data" select="$data"/>
      <xsl:with-param name="labels" select="$labels"/>
      <xsl:with-param name="cx" select="$cx"/>
      <xsl:with-param name="cy" select="$cy"/>
      <xsl:with-param name="r" select="$r"/>
      <xsl:with-param name="theta" select="$theta + $delta"/>
      <xsl:with-param name="sum" select="$sum"/>
      <xsl:with-param name="context" select="$context"/>
      <xsl:with-param name="i" select="$i + 1"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>
<!-- Überschreiben, um den Stil eines Tortenstücks zu ändern. -->
<xsl:template name="svgu:pieSliceStyle">
  <xsl:param name="i"/>
  <xsl:param name="context"/>
  <xsl:variable name="colors" select="document('')/*/svgu:color"/>
  <xsl:value-of select="concat('stroke: black; stroke-width: 0.5; fill: ',$colors[($i - 1 ) mod count($colors) + 1])"/>
</xsl:template>
<!-- Überschreiben, um den Stil einer Tortenstückbezeichnung zu ändern. -->
<xsl:template name="svgu:pieSliceLabelStyle">
  <xsl:param name="i"/>
  <xsl:param name="value"/>
  <xsl:param name="label" />
  <xsl:param name="context"/>
  <xsl:text>font-size: 16;</xsl:text>
</xsl:template>

Das folgende Stylesheet erzeugt aus Beispieldaten ein Tortendiagramm, wie es in der folgenden Abbildung gezeigt wird.

Ein generiertes Tortendiagramm

Abbildung: Ein generiertes Tortendiagramm.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">
  <xsl:include href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <test:data>1.0</test:data>
  <test:data>2.0</test:data>
  <test:data>3.0</test:data>
  <test:data>4.0</test:data>
  <test:data>5.0</test:data>
  <test:data>13.0</test:data>
  <xsl:template match="/">
  <svg:svg width="500" height="500">
    <xsl:call-template name="svgu:pie">
      <xsl:with-param name="data" select="document('')/*/test:data"/>
      <xsl:with-param name="cx" select="250"/>
      <xsl:with-param name="cy" select="250"/>
      <xsl:with-param name="r" select="100"/>
      <xsl:with-param name="theta" select="-90"/>
    </xsl:call-template>
    <xsl:call-template name="svgu:pieLabels">
      <xsl:with-param name="data" select="document('')/*/test:data"/>
      <xsl:with-param name="cx" select="250"/>
      <xsl:with-param name="cy" select="250"/>
      <xsl:with-param name="r" select="125"/>
      <xsl:with-param name="theta" select="-90"/>
    </xsl:call-template>
  </svg:svg>
</xsl:template>

Open-High-Low-Close-Plots

So genannte Open-High-Low-Close-Plots werden häufig verwendet, um Entwicklungen bei Wertpapieren (mit Eröffnungs- und Schlusskursen pro Tag) darzustellen, können aber auch in anderen Anwendungen benutzt werden (z.B., um Minimum, Maximum, Mittelwert und Median zu zeigen). Die Daten werden in Form von vier verschiedenen Knotenmengen an dieses Template übergeben, die jeweils eine Reihe darstellen. Davon wird nur die High- und die Low-Menge benötigt. Das Template kommt auch mit fehlenden Datenwerten in Reihen zurecht:

<xsl:template name="svgu:openHiLoClose">
  <xsl:param name="openData" select="/.."/>
  <xsl:param name="hiData" select="/.."/>
  <xsl:param name="loData" select="/.."/>
  <xsl:param name="closeData" select="/.."/>
  <xsl:param name="width" select=" '500' "/>
  <xsl:param name="height" select=" '500' "/>
  <xsl:param name="offsetX" select="0"/>
  <xsl:param name="offsetY" select="0"/>
  <xsl:param name="openCloseExtent" select="8"/>
  <xsl:param name="max">
    <xsl:call-template name="emath:max">
      <xsl:with-param name="nodes" select="$hiData"/>
    </xsl:call-template>
  </xsl:param>
  <xsl:param name="min">
    <xsl:call-template name="emath:min">
      <xsl:with-param name="nodes" select="$loData"/>
    </xsl:call-template>
  </xsl:param>
  <xsl:param name="context"/>
  <xsl:variable name="hiCount" select="count($hiData)"/>
  <xsl:variable name="loCount" select="count($loData)"/>
  <xsl:variable name="openCount" select="count($openData)"/>
  <xsl:variable name="closeCount" select="count($closeData)"/>
  <xsl:variable name="numBars" select="Math:min($hiCount, $loCount)"/>
  <xsl:variable name="spacing" select="$width div ($numBars + 1)"/>
  <xsl:variable name="range" select="$max - $min"/>
  <xsl:variable name="scale" select="$height div $range"/>
  <svg:g transform="translate({$offsetX},{$offsetY+$height}) scale(1,{-$scale}) translate(0,{-$min})">
    <xsl:for-each select="$hiData">
      <xsl:variable name="pos" select="position( )"/>
      <!-- Zeichne eine hi-lo-Linie -->
      <svg:line x1="{$spacing * $pos}" y1="{$loData[$pos]}" x2="{$spacing * $pos}" y2="{current( )}"id="{$context}_highLow_{$pos}">
        <xsl:attribute name="style">
          <xsl:call-template name="svgu:hiLoBarStyle">
            <xsl:with-param name="pos" select="$pos"/>
            <xsl:with-param name="context" select="$context"/>
          </xsl:call-template>
        </xsl:attribute>
      </svg:line>
      <!-- Zeichne eine open-Marke, falls Eröffnungsdaten vorhanden -->
      <xsl:if test="$openCount &gt;= $pos">
        <svg:line x1="{$spacing * $pos - $openCloseExtent}" y1="{$openData[$pos]}" x2="{$spacing * $pos}" y2="{$openData[$pos]}" id="{$context}_open_{$pos}">
          <xsl:attribute name="style">
            <xsl:call-template name="svgu:openCloseBarStyle">
              <xsl:with-param name="pos" select="$pos"/>
              <xsl:with-param name="scale" select="$scale"/>
              <xsl:with-param name="context" select="$context"/>
            </xsl:call-template>
          </xsl:attribute>
        </svg:line>
      </xsl:if>
      <!-- Zeichne eine close-Marke, falls Schlussdaten vorhanden -->
      <xsl:if test="$closeCount &gt;= $pos">
        <svg:line x1="{$spacing * $pos}" y1="{$closeData[$pos]}" x2="{$spacing * $pos +  $openCloseExtent}" y2="{$closeData[$pos]}" id="{$context}_close_{$pos}">
          <xsl:attribute name="style">
            <xsl:call-template name="svgu:openCloseBarStyle">
              <xsl:with-param name="pos" select="$pos"/>
              <xsl:with-param name="scale" select="$scale"/>
              <xsl:with-param name="context" select="$context"/>
            </xsl:call-template>
          </xsl:attribute>
        </svg:line>
      </xsl:if>
    </xsl:for-each>
  </svg:g>
</xsl:template>
<xsl:template name="svgu:hiLoBarStyle">
  <xsl:param name="pos"/>
  <xsl:param name="context"/>
  <xsl:text>stroke: black; stroke-width: 1 </xsl:text>
</xsl:template>
<xsl:template name="svgu:openCloseBarStyle">
  <xsl:param name="pos"/>
  <xsl:param name="scale"/>
  <xsl:param name="context"/>
  <xsl:text>stroke: black; stroke-width: </xsl:text>
  <xsl:value-of select="2 div $scale"/>
</xsl:template>

Mit dieser Routine können Sie z.B. Börsendaten wie in der folgenden Abbildung darstellen:

Ein mit XSLT generierter Open-High-Low-Close-Plot

Abbildung: Ein mit XSLT generierter Open-High-Low-Close-Plot.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" exclude-result-prefixes="svgu">
  <xsl:include href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <xsl:template match="/">
    <svg:svg width="600" height="400">
      <xsl:call-template name="svgu:openHiLoClose">
        <xsl:with-param name="openData" select="*/row/open"/>
        <xsl:with-param name="hiData" select="*/row/high"/>
        <xsl:with-param name="loData" select="*/row/low"/>
        <xsl:with-param name="closeData" select="*/row/close"/>
        <xsl:with-param name="min" select="30"/>
        <xsl:with-param name="max" select="80"/>
        <xsl:with-param name="width" select="600"/>
        <xsl:with-param name="height" select="350"/>
        <xsl:with-param name="offsetX" select="20"/>
        <xsl:with-param name="offsetY" select="20"/>
        <xsl:with-param name="boundingBox" select="1"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:yAxis">
        <xsl:with-param name="min" select="30"/>
        <xsl:with-param name="max" select="80"/>
        <xsl:with-param name="offsetX" select="20"/>
        <xsl:with-param name="offsetY" select="20"/>
        <xsl:with-param name="width" select="600"/>
        <xsl:with-param name="height" select="350"/>
      </xsl:call-template>
    </svg:svg>
  </xsl:template>
</xsl:stylesheet>

XSLT 2.0

Bei der Portierung dieser Rezepte von 1.0 nach 2.0 muss im Wesentlichen kaum etwas verändert werden. Da die Templates dann mehr Parameter erhalten, bekommt man weniger Laufzeitfehler, wenn man zu allen Parametern und Variablen eine Typangabe macht. Parameter, die eine Dimension beschreiben, z.B. Länge und Breite, sollten xs:double sein, und solche, die eine Anzahl beschreiben, z.B. die von Teilstrichen usw., sollten xs:integer sein. Einige Parameter sind xs:boolean, was aber klar ist, weil sie die vorgegebenen Werte true( ) bzw. false( ) annehmen.

Diskussion

Transformationen von XML nach SVG sind normalerweise nicht gerade trivial. Das grafische Layout der Daten erfordert eine sorgfältige Planung, selbst um die relativ bescheidenen Ergebnisse in den obigen Beispielen zu erhalten. Bei jeder XML-nach-SVG-Transformation wieder von vorn anzufangen, wäre töricht. Wesentlich besser ist es, wenn man mit einem Baukasten von Komponenten arbeitet. Ich habe mich hier auf Hilfskonstrukte für Diagramme beschränkt, aber Sie werden bestimmt eigene Komponenten für andere Bereiche finden. Die Technik zur Entwicklung dieser Bausteine besteht in der Aufteilung der Konstruktion einer Grafik in einzelne Komponenten und in der Erstellung von Templates für diese Komponenten, und zwar so, dass diese miteinander kombiniert werden können. Ein wesentlicher Punkt dabei ist der, dass alle Templates über genügend Informationen verfügen, um ihr jeweiliges Koordinatensystem so zu skalieren, dass sie zu Grafiken kompatibel bleiben, die von anderen unabhängigen Templates erstellt wurden. So enthalten die meisten dieser Templates die Parameter $min und $max, obwohl vernünftige Werte dafür aus den Eingabedaten berechnet werden können. Damit kann der Aufrufende die Vorgabewerte überschreiben, um den Datenbereich des Diagramms als Ganzes zu betrachten.

Eine der Festlegungen beim Entwurf dieser Templates bestand darin, zuzulassen, dass Stilangaben über Aufrufe von Standard-Templates erhalten werden können, die von einem importierenden Stylesheet überschrieben werden können. In vielen Fällen hätten diese Angaben mit weiteren Parametern gemacht werden können. Aber dieser Callback-basierte Ansatz wurde deswegen gewählt, weil er die Flexibilität bietet, Stilangaben abhängig von den zu plottenden Daten zu machen. Betrachten Sie z.B., wie die Tortenstücke oder Balken jederzeit abhängig vom gerade geplotteten Datenwert variieren können:

<xsl:template name="svgu:pieSliceStyle">
  <xsl:param name="i"/>
  <xsl:param name="context"/>
  <xsl:variable name="colors" select="document('')/*/svgu:color"/>
  <xsl:value-of select="concat('stroke:black; stroke-width: 0.5; fill: ',$colors[($i - 1 ) mod count($colors) + 1])"/>
</xsl:template>

Sie könnten sogar noch weitere Parameter vom Haupt-Template an solche Funktionen übergeben. Eine naheliegende Erweiterung wäre, den tatsächlichen Datenwert zu übergeben, damit dessen Größe z.B. die Farbe beeinflussen kann. Eine Einschränkung dieser Technik besteht darin, dass jedes Stylesheet ein Template nur einmal überschreiben kann. Dieser Abschnitt gleicht diese Einschränkung durch die Verwendung eines benutzerdefinierten Kontextparameters aus. Dieser Parameter ermöglicht es überschriebenen Templates, abhängig vom Kontext, ihr Verhalten zu ändern. Der Kontext hat einen doppelten Zweck: Sie können ihn als Ausgangsbasis für die Erzeugung von Bezeichnern für id-Attribute von SVG-Elementen benutzen. Das ist dann hilfreich, wenn Sie mit dem generierten SVG interagieren möchten (siehe das Rezept Interaktive SVG-basierte Webseiten erzeugen).

Das letzte Beispiel erzeugt ein komplexes Diagramm, das aus Open-High-Low-Close-Balken für Börsendaten, einem Balkendiagramm für den Umsatz, einem XY-Plot für den gleitenden Durchschnittsumsatz und zwei Y-Achsen für je eine Preis- und Umsatzskala besteht. Das Ergebnis sehen Sie in der Abbildung unten.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:emath="http://www.exslt.org/math" exclude-result-prefixes="svgu">
  <xsl:include href="svg-utils.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN" doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
  <xsl:variable name="width" select="600"/>
  <xsl:variable name="height" select="500"/>
  <xsl:variable name="pwidth" select="$width * 0.8"/>
  <xsl:variable name="pheight" select="$height * 0.8"/>
  <xsl:variable name="offsetX" select="($width - $pwidth) div 2"/>
  <xsl:variable name="offsetY" select="10"/>
  <xsl:variable name="dataMin">
    <xsl:call-template name="emath:min">
      <xsl:with-param name="nodes" select="//Low"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:variable name="dataMax">
    <xsl:call-template name="emath:max">
      <xsl:with-param name="nodes" select="//High"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:variable name="min" select="$dataMin * 0.9"/>
  <xsl:variable name="max" select="$dataMax * 1.1"/>
  <xsl:template match="/">
    <svg:svg width="{$width}" height="{$height}">
      <svg:text x="{$width div 2}" y="{2 * $offsetY}" style="text-anchor:middle; font-size:24">MSFT-Aktienkurs</svg:text>
      <svg:text x="{$width div 2}" y="{4 * $offsetY}" style="text-anchor:middle; font-size:12">23.05.2002 bis 16.08.2002</svg:text>
      <!-- PREIS -->
      <xsl:call-template name="svgu:openHiLoClose">
        <xsl:with-param name="openData" select="*/row/Open"/>
        <xsl:with-param name="hiData" select="*/row/High"/>
        <xsl:with-param name="loData" select="*/row/Low"/>
        <xsl:with-param name="closeData" select="*/row/Close"/>
        <xsl:with-param name="min" select="$min"/>
        <xsl:with-param name="max" select="$max"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="boundingBox" select="1"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:yAxis">
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$pheight"/>
        <xsl:with-param name="min" select="$min"/>
        <xsl:with-param name="max" select="$max"/>
        <xsl:with-param name="context" select=" 'price' "/>
      </xsl:call-template>
      <!-- VOLUMEN -->
      <xsl:variable name="vheight" select="100"/>
      <xsl:call-template name="svgu:bars">
        <xsl:with-param name="data" select="*/row/Volume"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$vheight"/>
        <xsl:with-param name="orientation" select="0"/>
        <xsl:with-param name="offsetX" select="$offsetX"/>
        <xsl:with-param name="offsetY" select="$pheight - $offsetY"/>
        <xsl:with-param name="barLabel" select="false( )"/>
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="1500000"/>
      </xsl:call-template>
      <!-- Hiermit beginnt der Linien-Plot auf dem ersten Balken und endet auf dem letzten Balken -->
      <xsl:variable name="spacing" select="$pwidth div count(*/row/High) + 1"/>
      <xsl:call-template name="svgu:xyPlot">
        <xsl:with-param name="dataY" select="*/row/Vol10MA"/>
        <xsl:with-param name="width" select="$pwidth - 2 * $spacing"/>
        <xsl:with-param name="height" select="$vheight"/>
        <xsl:with-param name="offsetX" select="$offsetX + $spacing"/>
        <xsl:with-param name="offsetY" select="$pheight - $offsetY"/>
        <xsl:with-param name="minY" select="0"/>
        <xsl:with-param name="maxY" select="1500000"/>
      </xsl:call-template>
      <xsl:call-template name="svgu:yAxis">
        <xsl:with-param name="offsetX" select="$width - $offsetX"/>
        <xsl:with-param name="offsetY" select="$height - $vheight - $offsetY"/>
        <xsl:with-param name="width" select="$pwidth"/>
        <xsl:with-param name="height" select="$vheight"/>
        <xsl:with-param name="min" select="0"/>
        <xsl:with-param name="max" select="1500000"/>
        <xsl:with-param name="context" select=" 'volume' "/>
      </xsl:call-template>
    </svg:svg>
  </xsl:template>
  <xsl:template name="svgu:barStyle">
    <xsl:text>stroke: black; stroke-wdth: 0.15</xsl:text>
  </xsl:template>
  <xsl:template name="svgu:xyPlotStyle">
    <xsl:param name="context"/>
    <xsl:param name="scale"/>
    <xsl:value-of select="concat('fill: none; stroke: black; stroke-width:',4 div $scale,'; ')"/>
  </xsl:template>
  <xsl:template name="yAxisLabelStyle">
    <xsl:param name="context"/>
    <xsl:choose>
      <xsl:when test="$context = 'price'">
        <xsl:text>text-anchor:end;font-size:8;baseline-shift:-50%</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>text-anchor:start;font-size:8;baseline-shift:-50%</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <!-- Verschiebe die Volume-Bezeichnungen weiter weg von den Teilstrichbezeichnungen -->
  <xsl:template name="yAxisLabelXOffset">
    <xsl:param name="context"/>
    <xsl:if test="$context = 'volume'">
      <xsl:value-of select="6"/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Eine komplexe Kombination von Diagrammen

Abbildung: Eine komplexe Kombination von Diagrammen.

  

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de