Start - Publikationen - Wissen - TOGAF - Impressum -

Einleitung


Abbildungen, die im Vektorformat definiert sind, können in Webanwendungen erhebliche Vorteile gegenüber Pixel basierten Grafiken bieten:

  • Die Grafiken skalieren, das heißt sie sind in jeder gewünschten Auflösung darstellbar.
  • Vektorgrafiken können sehr kompakt sein und erfordern dann wenig Bandbreite.
  • Die Daten sind textbasiert und können zur Laufzeit erzeugt werden.
  • Die Entwicklung von Vektorgrafiken kann über Versionierung optimal mit der Entwicklung anderer Projektartefakte organisiert werden. Vektorgrafiken sind demnach einfacher wartbar und erweiterbar.
Die grafische Präsentation von Daten in Reports, Straßenkarten oder Netzdiagramme sind ein paar Beispiele für den Einsatz von Vektorgrafik in Webanwendungen.

SVG, der W3C Standard für Vektorgrafik im WWW, ist die Methode der Wahl, um Vektorgrafiken in eine Webanwendung einzubetten. Als Gründe sehe ich:

  • Die Spezifikation ist offen verfügbar und wird von einem neutralen Konsortium weiterentwickelt.
  • Die Spezifikation stellt sicher, dass die fertig gerenderten Grafiken überall gleich aussehen.
  • SVG Renderer sind in praktisch allen modernen HTML Browsern integriert.
  • Das Rendern von SVG stellt keine großen Anforderungen an die Hardware der Ausführungsumgebung und kann zum Beispiel auf mobilen Geräten problemlos erfolgen.
In diesem Artikel gebe ich eine Analyse zum Umgang mit dem use-Element in SVG. Das use-Element ist die Grundlage für die Wiederverwendung von Grafiken, die an anderer Stelle (im selben Dokument oder in einem anderen Dokument) definiert sind.

Der symbol-Block


Die Verwendung des use-Elements ist zunächst einmal sehr einfach. Per ID wird auf eine Gruppe von Anweisungen referenziert:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
: 
  <!-- Definition einer Gruppe von grafischen Anweisungen -->
  <symbol id="someID">
    <g>
      : grafische Anweisungen
    </g>
  </symbol>
:
  <!-- Einbindung der Anweisung per use-Element -->
  <use xlink:href="#someID" />
:
</svg>
Die in someID genutzten Koordinatenangaben beziehen sich dabei auf den ersten erreichbaren Rendering Kontext. In diesem Falle wäre das der Kontext der Einbettung der gesamten SVG (und dort zum Beispiel die Angaben im viewBox-Attribut), dann steht fest, auf welche Breite und Höhe sich die Koordinaten beziehen. Das ist in keinem Fall erwünscht und deshalb müssen immer Angaben im viewBox-Attribut den Rendering Kontext vorgeben:
: 
  <symbol id="someID" viewBox="-50 -50 100 100">
    <g>
      : grafische Anweisungen
    </g>
  </symbol>
:
Die ersten beiden Zahlen repräsentieren die Position des Koordinatenursprungs der Viewbox dieser Grafik, die letzten beiden Zahlen die Breite und Höhe der Umgebung, auf die sich ein Rendern der Grafik zu beziehen hat. Im Beispiel werden damit alle Koordinatenangaben auf den "Mittelpunk" der Grafik bezogen. Das ist sehr praktisch, wenn man grafische Elemente mit Symmetrien definiert (viele Koordinaten werden dann einfach gespiegelt) oder wenn man die Grafik vor dem Verwenden um ihr Zentrum drehen oder an einer Symmetrieachse spiegeln möchte.

Fazit: Grafiken, die für Wiederverwendung entworfen werden

  1. kommen in ein symbol-Block und werden mit einer ID versehen
  2. erhalten über das viewBox-Attribut explizit einen Rendering Kontext
  3. haben ihren Koordinatenursprung im Symmetriezentrum respektive auf Symmetrieachsen der Grafik - das vereinfacht die Erstellung der Grafik und fördert ihre intuitive Nutzung
Die vier Angaben in im viewBox-Attribut nenne ich in diesem Artikel vb_x, vb_y, vb_w und vb_h.

Das use-Element


Ein use-Element

: 
  <use xlink:href="#someID" x=".." y=".." width=".." height=".." transform=".."/>
:
hat drei verschiedene Stellen, die eine Transformation der referenzierten Grafik zur Folge haben können:
  1. Die Angaben width und height legen die Größe des Ausgabebereichs fest, die Grafik wird auf diese Werte skaliert. Die dafür erforderliche Transformation ist eine Skalierung scale(s_x s_y) mit s_x=width/vb_w und s_y=height/vb_h.
  2. Die Angaben x und y bedingen eine Translation translate(x y) der referenzierten Grafik.
  3. Die Angabe im transform Attribut bedingt eine vom Nutzer vorgegebene Transformation T.
Das Problem ist, dass nicht ohne Weiteres klar ist, in welcher Reihenfolge diese drei Transformationen angewendet werden und damit, wie sie sich gegenseitig beeinflussen. Ein Symptom dafür: man schraubt an diesen Werten so lange, bis es irgendwie passt, bei der nächsten Verwendung der selben Grafik wiederholt sich das dann. Im Folgenden werden ich die Reihenfolge der Transformationen erklären und ein Rezept für die richtige Wahl der Attribute im use-Element, insbesondere der Wahl für T, vorschlagen.

Die Reihenfolge der Transformationen ist diese:

T * translate(x y) * scale(s_x s_y) * translate(-vb_x -vb_y)
  1. Ganz rechts (diese Transformation kommt als erstes zum Einsatz) wird mit den Angaben der Viewbox in der referenzierten Grafik der Koordinatenursprung der Grafik auf den Koordinatenursprung des use-Elements gesetzt.
  2. Anschließend wird die Grafik mit scale(s_x s_y) auf die richtige Größe gebracht.
  3. Danach werden die Wert für x und y als Transformation eingesetzt.
  4. Ganz links steht die vom Nutzer festgelegte Transformation T, sie kommt zuletzt zum Einsatz. Die Wirkung dieser Transformation (drehen, spiegeln etc.) soll aber in praktisch allen relevanten Fällen mit Bezug zu den Koordinaten der referenzierten Grafik erfolgen (und das ist es, was man als Nutzer auch erwartet).
Die Transformationen 1-3 werden beim Rendern automatisch zur Anwendung gebracht. Das Problem als Frage formuliert: wie muss die vom Nutzer vorgegeben Transformation T beschaffen sein, dass Drehungen und Spiegelungen in Bezug auf die referenzierte Grafik erfolgen und die skalierte und transformierte Grafik mit ihrem Mittelpunkt dann an vorgegebenen Koordinaten erscheint?

Als Lösung wird x=y="0" gewählt und die Transformation T umgeschrieben in

T = scale(s_x s_y) * T' * translate(vb_x vb_y) * scale(1/s_x 1/s_y)
T' kann nun mit Bezug auf die referenzierte Grafik und deren Koordinatenursprung definiert werden. Die Verschiebung der Grafik an den Punkt (x,y) wird dann noch mit einer abschließenden Translation erledigt:
T = translate(x y) * scale(s_x s_y) * T' * translate(vb_x vb_y) * scale(1/s_x 1/s_y)
Im fertigen use-Element kommt der Rezeptcharakter klarer zum Vorschein: Der Audruck
<use xlink:href="#someID" width="w" height="h" 
     transform="translate(x y) scale(s_x s_y) T' translate(vb_x vb_y) scale(1/s_x 1/s_y)"/>   
rendert eine Grafik mit viewBox="vb_x vb_y vb_w vb_h" in den Maßen w und h mit dem Koordinatenursprung der Grafik bei (x y). T' steht für eine Nutzer-definierte Transformation, die sich auf die Koordinaten der referenzierten Grafik bezieht. Sieht komplizierter aus als es ist, aber die Werte für vb_x, vb_y, s_x=w/vb_w und s_y=h/vb_h sind ja fest vorgegeben und müssen nur blind eingesetzt werden. Darüber hinaus gibt es keine einfachere Variante, da die unglückliche Reihenfolge der automatisch angewendeten Transformationen nicht zu ändern ist.

Kann die Grafik in originaler Größe dargestellt werden, so vereinfacht sich das Rezept. Dann ist s_x=w/vb_w=1 und s_y=h/vb_h=1 und man erhält

<use xlink:href="#someID" width="vb_w" height="vb_h" transform="translate(x y) T' translate(vb_x vb_y)"/>   

Beispiel


Zunächst definieren wir eine Grafik für ein stilisiertes Flugzeug, mit seinem (angenommenen) Schwerpunkt bei (0,0), das wir anschließend skaliert, verschoben und rotiert wiederverwenden wollen:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" 
  xmlns:xlink="http://www.w3.org/1999/xlink" 
  viewBox="0 0 800 200">
  <!-- 
      ein Flugzeug-Icon mit dem Schwerpunkt bei (0,0) 
  -->
  <symbol id="airplane.icon" viewBox="-50 -50 100 100">
    <!-- Alle Koordinatenangaben beziehen sich auf die angegebene Viewbox -->
    <polygon points="-13,38 -13,32 -3,30 -3,11 -45,7 -45,-5 -4,-10 -4,-21 
      0,-25 4,-21 4,-10 45,-5 45,7 3,11 3,30 13,32 13,38"
      style="fill:grey;stroke-width:4;stroke-linejoin:round"/>
    <line x1="-8" y1="-24"  x2="8" y2="-24" style="stroke:grey;stroke-width:2;stroke-linecap:round"/>
    <line x1="-15" y1="0"  x2="15" y2="0" style="stroke:black;stroke-width:2;stroke-linecap:round"/>
    <line x1="0" y1="-15"  x2="0" y2="15" style="stroke:black;stroke-width:2;stroke-linecap:round"/>
    <circle cx="-42" cy="-2" r="2" fill="red" />      
    <circle cx="42" cy="-2" r="2" fill="green" />
  </symbol>
  <!--
      Nutzung mit dem use-Element
  -->
  <use xlink:href="#airplane.icon" width="200" height="200"/>   
  <!-- 
      Koordinatenachsen zur Besseren Orientierung 
  -->
  <g id="coordgrid" style="stroke:black;stroke-width:0.5" transform="scale(2)">
    <line x1="0" y1="10"  x2="100" y2="10"/>
    : 16 mehr davon
    <line y1="0" x1="90"  y2="100" x2="90"/>
  </g>
</svg>
SVG wird von Ihrem Browser nicht unterstützt.

Wie erwartet sehen wir die Grafik nicht bei (0,0), sondern bei (100,100). Denn nach dem gegebenen Rezept haben wir eigentlich

:
  <use xlink:href="#airplane.icon" width="200" height="200"
       transform="translate(100 100) scale(2) translate(-50 -50) scale(0.5)"/>
:
Um das Flugzeug zu rotieren testen wir
:
  <use xlink:href="#airplane.icon" width="200" height="200" transform="rotate(45)"/>
:
SVG wird von Ihrem Browser nicht unterstützt.

und erhalten das falsche Ergebnis: die Rotation bezieht sich auf die Koordinaten des use-Elements was in den meisten Situationen nutzlos erscheint. Mit dem angegebenen Rezept erhalten wir jedoch das gewünschte Ergebnis:

:
  <use xlink:href="#airplane.icon" width="200" height="200" 
    transform="translate(150 100) scale(2) rotate(45) translate(-50 -50) scale(0.5)"/>   
  <use xlink:href="#airplane.icon" width="100" height="100" 
    transform="translate(50 50) rotate(90) translate(-50 -50) "/>   
  <use xlink:href="#airplane.icon" width="50" height="50" 
    transform="translate(100 100) scale(0.5) rotate(180) translate(-50 -50) scale(2)"/>    
:
SVG wird von Ihrem Browser nicht unterstützt.

Fazit


Grafiken können in SVG mit dem symbol - use Konstrukt wiederverwendet werden. Erst das angegebene Rezept erlaubt es jedoch

  1. eine Grafik in "intuitiven" Koordinaten, das heißt mit dem Koordinatenursprung im Schwerpunkt oder/und in Symmetrieachsen des Objekts zu definieren, um dann diese Grafik
  2. einer Transformation bezogen auf diesen Koordinatenursprung und den Koordinaten dieser Grafik zu unterziehen und sie an Koordinaten im use-Kontext zu verschieben.
Die Attribute x und y können dabei nicht genutzt werden, da die betreffende Transformation an einer Stelle vom SVG Renderer eingesetzt wird, die einem intuitiven Gebrauch entgegen steht. Das besprochene Rezept wäre überflüssig, wenn die Reihenfolge der angewandten Transformationen (im beschriebenen Sinne)
translate(x y) * scale(s_x s_y) * T
wäre. Das ist sie aber nicht.
copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved