Dynamische Zeichnungen und Diagramme: Mit SVG!

Sie möchten dynamische Zeichnungen, Diagramme oder Geschäftsgrafiken erstellen und die vorhandenen Möglichkeiten (SVG-Diagramme)  von Application Express reichen Ihnen nicht aus? Kein Problem!

In der letzten Ausgabe haben Sie erfahren, wie Sie einfache, dynamische SVG-Zeichnungen in Ihre Application Express-Anwendungen einbetten können. Heute nutzen Sie dieses Wissen, um - basierend auf Tabellendaten - eigene SVG-Diagramme wie in Abbildung 1 zu erstellen. Vorab jedoch: Um dies zu erreichen, muss etwas PL/SQL-Code geschrieben werden.

Ausgangssituation: Bericht auf die Tabelle SH.SALES

Abb.1: Das Ziel: Eigene SVG-Grafiken 

Abbildung 2 zeigt die Tabellendaten, für die das Diagramm erstellt werden soll. Das Skript, mit dem die Tabelle erstellt wurde, finden Sie hier. Das Einkommen soll auf der X-Achse, das Alter auf der Y-Achse aufgetragen werden. Zustimmung soll grün, Ablehnung rot und Unentschlossenheit (SQL NULL) grau dargestellt werden.

Ausgangssituation: Tabellendaten

Abb.2: Ausgangssituation: Tabellendaten

Zunächst macht es Sinn, sich ein wenig Gedanken um die Aufteilung der Fläche im SVG-Diagramm zu machen (Abbildung 3). Neben der eigentlichen Diagrammfläche wird Platz für die Beschriftung der X- und Y-Achsen sowie für einen Rahmen um alles benötigt.

Aufteilung der Diagrammfläche ...

Abb.3: Aufteilung der Diagrammfläche ...

Im ersten Schritt sollen die Achsen und die Diagrammfläche gezeichnet werden. Die Größe des Diagramms, des Rahmens und der Beschriftungsfläche wird zunächst in Variablen hinterlegt. Lassen Sie den folgenden Code im SQL-Workshop laufen. Dabei wird eine Prozedur namens SHOW_UMFRAGE_SVG erzeugt.

create or replace procedure show_umfrage_svg
is
v_svg_width number := 500;
v_svg_height number := 400;
v_svg_margin number := 15;
v_svg_textbar_left number := 30;
v_svg_textbar_bottom number := 15;

v_svg_bgcolor varchar2(6) := 'EEEEEE';

begin
owa_util.mime_header('image/svg+xml', false);
owa_util.http_header_close;

htp.p('<?xml version="1.0" standalone="yes"?>');
htp.p('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">');
htp.p('<svg width="'||v_svg_width||'px" ');
htp.p(' height="'||v_svg_height||'px" ');
htp.p(' version="1.1" ');
htp.p(' xmlns="http://www.w3.org/2000/svg">');
--
-- Hier wird das "Koordinatensystem dargestellt"
-- 1. Y-Achse ...
--
htp.p('<line stroke="black" ');
htp.p(' width="1pt"');
htp.p(' x1="'||(v_svg_textbar_left + v_svg_margin)||'" ');
htp.p(' y1="'||(v_svg_margin)||'" ');
htp.p(' x2="'||(v_svg_margin + v_svg_textbar_left)||'" ');
htp.p(' y2="'||(v_svg_height - v_svg_textbar_bottom - v_svg_margin)||'"/>');
--
-- 2. X-Achse ...
--
htp.p('<line stroke="black" ');
htp.p(' width="1pt" ');
htp.p(' x1="'||(v_svg_margin + v_svg_textbar_left)||'" ');
htp.p(' y1="'||(v_svg_height - v_svg_margin - v_svg_textbar_bottom)||'" ');
htp.p(' x2="'||(v_svg_width - v_svg_margin)||'" ');
htp.p(' y2="'||(v_svg_height - v_svg_margin - v_svg_textbar_bottom)||'" />');
--
-- 3. Diagrammfläche grau hinterlegen
--
htp.p('<rect x="'||(v_svg_textbar_left + v_svg_margin)||'" ');
htp.p(' y="'||(v_svg_margin)||'" ');
htp.p(' width="' ||(v_svg_width - (2 * v_svg_margin) - v_svg_textbar_left)||'" ');
htp.p(' height="'||(v_svg_height - (2 * v_svg_margin) - v_svg_textbar_bottom)||'" ');
htp.p(' fill="#' ||(v_svg_bgcolor)||'"/>');

htp.p('</svg>');
end;
/

Die Prozedur definiert zunächst Variablen für die Größe des Diagramms (v_svg_width, v_svg_height), die Rahmenbreite (v_svg_margin) und die Größe der Achsenbeschriftung (v_svg_textbar_left, v_svg_textbar_bottom). Mit diesen Angaben werden dann die Achsen des Diagramms mit der SVG-Direktive <line> (Dokumentation) gezeichnet. Zum Abschluß wird die Diagrammfläche mit <rect> (Dokumentation) grau gefüllt. Um dieses Diagramm in Ihrer Application Express-Anwendung darstellen zu können, benötigen Sie nun einen Anwendungsprozeß und eine Region. 

Navigieren Sie zu den Gemeinsamen Komponenten, dort zu den Anwendungsprozessen und hinterlegen Sie dort einen Bedarfsgesteuerten Anwendungsprozeß (Abbildung 4). Als PL/SQL-Code hinterlegen Sie einfach nur einen Aufruf der soeben angelegten PL/SQL-Prozedur SHOW_UMFRAGE_SVG.


Code des Bedarfsgesteuerten Anwendungsprozesses

Abb.4: Code des Bedarfsgesteuerten Anwendungsprozesses

Erzeugen Sie nun auf Ihrer Anwendungsseite eine neue HTML-Region und geben Sie als HTML-Quelltext folgendes ein:

<OBJECT 
width="500"
height="400"
type="image/svg+xml"
data="f?p=&APP_ID.:&APP_PAGE_ID.:&SESSION.:APPLICATION_PROCESS=showUmfrageSVG" >
</OBJECT>

Nun können Sie die Seite starten - Das Ergebnis sollte wie folgt aussehen:

Ergebnis des 1. Schritts: Zeichnen der Achsen und der Diagrammfläche

Abb.5: Ergebnis des 1. Schritts: Zeichnen der Achsen und der Diagrammfläche

Nun geht es daran, die Achsen zu beschriften. Geben Sie das jeweilige Minimum und Maximum für den Moment ebenfalls noch in Variablen vor. Die Prozedur lässt sich später natürlich noch erweitern, so dass diese Werte aus den Tabellendaten automatisch ermittelt werden. Nehmen Sie also in den DECLARE-Block der Prozedur noch auf ...

v_y_min           number := 18;
v_y_max number := 100;
v_x_min number := 500;
v_x_max number := 10000;

v_y_label varchar2(10) := 'ALTER';
v_x_label varchar2(10) := 'EINKOMMEN';

Zum Beschriften des Diagramms wird die SVG-Direktive <text> (Dokumentation) verwendet. Fügen Sie in den Programmblock vor der Zeile htp.p('</svg>'); folgendes ein:

  --
-- Maximum der Y-Achse
--
htp.p('<text text-anchor="end" ');
htp.p(' dominant-baseline="hanging" ');
htp.p(' x="'||(v_svg_margin+v_svg_textbar_left)||'" ');
htp.p(' y="'||(v_svg_margin)||'">');
htp.p(v_y_max)
htp.p('</text>');

--
-- Minimum der Y-Achse
--
htp.p('<text text-anchor="end" ');
htp.p(' x="'||(v_svg_margin+v_svg_textbar_left)||'" ');
htp.p(' y="'||(v_svg_height - v_svg_margin - v_svg_textbar_bottom)||'">');
htp.p(v_y_min);
htp.p('</text>');

--
-- Maximum der X-Achse
--
htp.p('<text text-anchor="end" ');
htp.p(' x="'||(v_svg_width - v_svg_margin)||'" ');
htp.p(' y="'||(v_svg_height - v_svg_margin)||'">');
htp.p(v_x_max);
htp.p('</text>');

--
-- Minimum der X-Achse
--
htp.p('<text x="'||(v_svg_margin + v_svg_textbar_left)||'" ');
htp.p(' y="'||(v_svg_height - v_svg_margin)||'">');
htp.p(v_x_min);
htp.p('</text>');

--
-- Beschriftung der X-Achse (Mitte der Diagrammfläche)
--
htp.p('<text text-anchor="middle" ');
htp.p(' x="'||(
(v_svg_margin + v_svg_textbar_left) +
(v_svg_width - (2 * v_svg_margin) - v_svg_textbar_left) / 2
)||'" ');
htp.p(' y="'||(v_svg_height-v_svg_margin)||'">');
htp.p(v_x_label);
htp.p('</text>');

--
-- Beschriftung der Y-Achse (Mitte der Diagrammfläche)
--
htp.p('<text writing-mode="tb-rl" ');
htp.p(' dominant-baseline="hanging" ');
htp.p(' text-anchor="middle" ');
htp.p(' x="'||(v_svg_margin + v_svg_textbar_left)||'" ');
htp.p(' y="'||round(
(v_svg_margin +
(v_svg_height - (2 * v_svg_margin) - v_svg_textbar_bottom) / 2
),0
)||'">');
htp.p(v_y_label);
htp.p('</text>');

Zur Ausrichtung und Formatierung des jeweiligen Textes kennt <text> verschiedene Attribute. Hier wurden folgende verwendet:

  • dominant-baseline (Dokumentation):
    Dieses Attribut gibt die Baseline des Textes, also eine Linie, anhand derer der Text vertikal ausgerichtet wird, an. Der Wert hanging bedeutet, dass die einzelnen Buchstaben quasi aufgehangen werden; man könnte es also auch übersetzen in "Vertikale Ausrichtung nach oben".
  • text-anchor (Dokumentation):
    ... beschreibt die horizontale Ausrichtung des Textes. Die effektive Auswirkung ist Linksbündig, zentriert oder rechtsbündig.
  • writing-mode (Dokumentation):
    ... beschreibt, wie der Text geschrieben werden soll . Der hier verwendete Wert tb-rl meint, dass von oben nach unten und dann von links nach rechts geschrieben werden soll. Standardmäßig wird lr-tb geschrieben, also von links nach rechts und dann von oben nach unten.

Schauen Sie sich danach die Anwendungsseite erneut im Browser an. Das Ergebnis sollte in etwa wie folgt aussehen:

Ergebnis des 2. Schritts: Beschriftung des Diagramms
Abb.6: Ergebnis des 2. Schritts: Beschriftung des Diagramms

Nun ist die "Vorarbeit" gemacht. Dem Diagramm fehlen nur noch die eigentlichen Datenpunkte. Definieren Sie zunächst ein paar Hilfsvariablen:

  v_svg_x_null          number;
v_svg_y_null number;
v_svg_x_step number;
v_svg_y_step number;

v_circle_radius number := 5;

v_circle_width gibt an, wie groß ein Datenpunkt dargestellt werden soll (5 Pixel). v_svg_x_null und v_svg_y_null speichern die Null-Linie der jeweiligen Achse, also an welcher Pixelposition das Minimum der jeweiligen Achse aufgetragen wird. v_svg_x_step und v_svg_y_step speichern die Schrittweite - sie geben an, wieviel Pixeln eine Erhöhung desjeweiligen Wertes um 1 entspricht. Die Werte werden zunächst ermittelt ...

  -- 
-- Datenpunkte: Position der Null-linie bestimmen.
--
v_svg_y_null := v_svg_height - v_svg_margin - v_svg_textbar_bottom;
v_svg_x_null := v_svg_margin + v_svg_textbar_left;

--
-- Datenpunkte: "Schrittweite" bestimmen
--
v_svg_x_step := (v_svg_width - (2 * v_svg_margin) - v_svg_textbar_left) /
(v_x_max - v_x_min);
v_svg_y_step := -( (v_svg_height - (2 * v_svg_margin) - v_svg_textbar_bottom) /
(v_y_max - v_y_min)
);

... und dann beim Zeichnen der Datenpunkte genutzt. An diesem Punkt kommt endlich die SQL-Abfrage auf die Tabelle UMFRAGE zum Einsatz. Die Punkte werden mit der Directive <circle> (Dokumentation) als Kreise mit einem Durchmesser von 5 Pixeln dargestellt. 

  for werte in (
select
"ALTER" as y,
"MTL_EINKOMMEN" as x,
case
when "STIMME_ZU" = 'Y' then '00FF00'
when "STIMME_ZU" = 'N' then 'FF0000'
when "STIMME_ZU" is null then 'CCCCCC'
end as "WERT"
from umfrage
) loop
htp.p('<circle cy="'||(v_svg_y_null + ((werte."Y" - v_y_min) * v_svg_y_step))||'" ');
htp.p(' cx="'||(v_svg_x_null + ((werte."X" - v_x_min) * v_svg_x_step))||'" ');
htp.p(' r="'||v_circle_radius||'" ');
htp.p(' stroke="black" ');
htp.p(' fill="#'||werte."WERT"||'"/>');
end loop;

Das endgültige Ergebnis sieht dann wie folgt aus:

Endergebnis: Das fertige Diagramm
Abb.7: Endergebnis: Das fertige Diagramm

Sie finden den vollständigen Code der Prozedur hier. Wie Sie sehen, können Sie durch entsprechende Programmierung beliebige Diagrammtypen erzeugen - der Phantasie sind keine Grenzen gesetzt.

Zurück zur Community-Seite