Logo Oracle Deutschland   Application Express Community
Minidiagramme in einem APEX-Bericht mit HTML5 und SVG
Erscheinungsmonat APEX-Version Datenbankversion
Februar 2012 ab 4.0 ab 10.2

Der eine oder andere Entwickler kennt die Berichts-Formatmaske PCT_GRAPH mit der sich sehr schnell und elegant eine Mini-Balkengrafik in einen APEX-Bericht setzen lässt.

APEX-Bericht mit Formatmaske PCT_GRAPH

Abbildung 1: APEX-Bericht mit Formatmaske PCT_GRAPH

Allerdings sind die Möglichkeiten hier doch sehr eingegrenzt. Mehr als der einfache Balken ist nicht drin - das einzige, was sich noch festlegen lässt, ist die Breite und die verwendeten Farben. Schöner wäre es doch, wenn auch mehr Grafikelemente möglich wären ...

APEX-Bericht mit HTML5 Grafikelementen

Abbildung 2: APEX-Bericht mit HTML5-Grafikelementen

In diesem Tipp erfahren Sie, wie Sie solche "Minidiagramme" mit Hilfe von SVG und HTML5 in Ihre APEX-Berichte aufnehmen können. Mit dem neuen Standard HTML5 kommt SVG "zurück", denn es wird Teil des von den Browsern unterstützten HTML Sprachumfangs und damit nativ vom Browser unterstützt. Bereits heute wird SVG von den aktuellen Versionen von Firefox (4.0 und höher), Chrome oder Safari unterstützt - die Diagramme werden ohne Plugin direkt dargestellt. Der Microsoft Internet Explorer unterstützt SVG erst ab Version 9.0 - für die im Unternehmen vielfach noch eingesetzten Versionen 6, 7 und 8 gibt es allerdings ein JavaScript-"Plugin", welches das SVG in Flash "umwandelt", so dass es mit dem Adobe Flash Player dargestellt werden kann.

Nutzer von Firefox (ab 4.0), Chrome oder Safari können den Tipp also ohne weiteres umsetzen und können dann im Kapitel SVG auf einer APEX-Seite: Erste Schritte weiterlesen. Wer den Internet Explorer 6, 7 oder 8 oder ältere Firefox-Versionen (3.x) unterstützen möchte, sollte vorher das nächste Kapitel SVG in älteren Browsern lesen.

SVG in älteren Browsern

Ältere Browser wie Firefox 3.x oder Microsoft Internet Explorer 6, 7 oder 9 können in HTML eingebettetes SVG nicht direkt darstellen; wenn man nichts tut, bleiben die entsprechenden Stellen der APEX-Anwendung leer. Abhilfe schafft die OpenSource Software svgweb, die von der Webseite bei code.google.org heruntergeladen werden kann. Die Software wird als JavaScript-Bibliothek in die Webseite eingebunden. Wenn sie einen Browser erkennt, der SVG nicht direkt unterstützt, wird sie aktiv und "wandelt" das SVG in Flash um - so dass der Adobe Flash Player die Darstellung übernehmen kann. Kann das SVG vom Browser direkt dargestellt werden, wird nichts unternommen. Weitere Details können der SVGWEB-Webseite entnommen werden. Laden Sie also das svgweb-Paket herunter (Abbildung 3).

Download des Pakets "svgweb"

Abbildung 3: Download des Pakets "svgweb"

Packen Sie das ZIP Archiv auf Ihrem lokalen PC aus und betrachten Sie den Unterordner src (Abbildung 4).

Das ausgepackte ZIP-Archiv "svgweb"

Abbildung 4: Das ausgepackte ZIP-Archiv "svgweb"

Die hier enthaltenen Dateien (ohne den Ordner tools) müssen auf den APEX-Webserver unterhalb des Verzeichnisses images gelegt werden. Das Hochladen in den APEX Workspace als Statische Dateien reicht nicht aus. Wenn Sie also den Apache Webserver mit mod_plsql verwenden ...

  • schauen sie nach, auf welches Betriebssystemverzeichnis der Alias /i/ abgebildet ist
  • navigieren Sie dorthin, legen Sie darin ein Unterverzeichnis svgweb an
  • kopieren Sie die Dateien dort hinein.

Wenn Sie das PL/SQL Embedded Gateway verwenden, brauchen Sie zumindest zeitweilig DBA-Privilegien ...

  • Legen Sie sich einen Datenbankuser für die FTP-Kommunikation an - nennen Sie bspw. APXFTP
  • Geben Sie dem neuen Datenbankuser die Rolle XDBADMIN
  • Aktivieren Sie den FTP-Zugang zum Datenbankrepository: Rufen Sie dazu DBMS_XDB.SETFTPPORT auf. Als Port bietet sich die 2100 an.
  • Nun können Sie eine DOS-Kommandozeile öffnen, ins Verzeichnis src Ihrer svgweb-Ordnerstruktur wechseln und die Inhalte wie folgt ins APEX-Repository hochladen.
    D:\svgweb\src>ftp -n
    ftp> open sccloud030 2100
    Connected to sccloud030.de.oracle.com.
    220- sccloud030.de.oracle.com
    Unauthorised use of this FTP server is prohibited and may be subject to civil and criminal prosecution.
    220 sccloud030.de.oracle.com FTP Server (Oracle XML DB/Oracle Database) ready.
    ftp> user apxftp apxftp
    331 pass required for APXFTP
    230 APXFTP logged in
    ftp> cd images
    250 CWD Command successful
    ftp> mkdir svgweb
    257 MKD Command successful
    ftp> cd svgweb
    250 CWD Command successful
    ftp> bin
    200  Type set to I.
    ftp> prompt off
    Interactive mode Off .
    ftp> mput *
    200 PORT Command successful
    150 BIN Data Connection
    226 BIN Transfer Complete
    ftp: 81 bytes sent in 0,00Seconds 81000,00Kbytes/sec.
    
    :
    
    ftp: 52363 bytes sent in 0,00Seconds 52363000,00Kbytes/sec.
    Error opening local file tools.
    ftp> bye
    

    In APEX können Sie die Dateien dann mit #APP_IMAGES#svgweb/{dateiname} angesprochen werden. Als nächstes navigieren Sie zu Ihrer Anwendung und dort zum verwendeten Seitentemplate. Im Template Header binden Sie die svgweb-Bibliothek nach dem JavaScript-Code für APEX selbst wie in Abbildung 5 dargestellt, ein.

    Einbinden der svgweb Bibliothek ins Seitentemplate der APEX-Anwendung

    Abbildung 5: Einbinden der svgweb Bibliothek ins Seitentemplate der APEX-Anwendung

      <script src="#IMAGE_PREFIX#svgweb/svg.js" data-path="#IMAGE_PREFIX#svgweb/" type="text/javascript"></script>
    

    Damit sind die Vorbereitungen abgeschlossen. Ihre SVG-Grafiken sollten nun auch im Internet Explorer dargestellt werden. Zeit, die ersten Schritte mit HTML5 und SVG zu unternehmen ...

    SVG auf einer APEX-Seite: Erste Schritte ...

    Der erste Versuch einer SVG-Grafik wird noch recht einfach: Legen Sie eine HTML-Region an - in diese werden wir eine erste SVG-Grafik hineinsetzen - das ist im Moment zwar noch alles statisch, aber Sie bekommen den ersten Eindruck, wie das funktioniert. Hinterlegen Sie diesen HTML- bzw. SVG-Code als Regionsquelle.

    <p>
     Der erste Versuch: SVG in einer APEX-Anwendung
    </p>
    <script type="image/svg+xml">
    <svg width="200" height="200"
         style="background-color: #D2B48C; display: block; margin-bottom: 5px;">
      <rect x="0" y="0" width="140" height="360" fill="#AAAAAA" />
      <rect x="20" y="130" width="100" height="200" fill="#FF8800" />
      <circle id="c1" r="50" cx="70" cy="70"  fill="#880088" />
    </svg>
    </script>
    

    Starten Sie die Seite - sie sollte wie in Abbildung 6 aussehen ... und wenn Sie die "svgweb" Bibliothek richtig eingespielt haben, wird das auch vom Internet Explorer dargestellt.

    Der erste Schritt mit SVG - eine kleine Grafik auf der APEX-Seite

    Abbildung 6: Der erste Schritt mit SVG - eine kleine Grafik auf der APEX-Seite

    Dieser Tipp soll nun keine Referenz für den SVG Befehlsumfang sein - hierfür sind im Internet zahlreiche Tutorials vorhanden. Als Beispiel sei das Tutorial auf der Webseite www.w3schools.com genannt. Dort können Sie sich einen guten Überblick über die vorhandenen Direktiven verschaffen. Die Einbindung sollte immer wie in obigem Codebeispiel erfolgen ...

    <script type="image/svg+xml">
    <!-- SVG Code hier -->
    </script>
    

    Solche statischen SVG-Diagramme sind jedoch für die Anwendungsentwicklung nicht sehr hilfreich. Als nächstes wollen wir das XML für die SVG-Grafik also dynamisch mit Daten aus Tabellen generieren ...

    Dynamische SVG-Diagramme mit PL/SQL

    Der nächste Schritt ist nun das Erstellen einer PL/SQL-Prozedur, die, anhand von Parametern, das XML für ein SVG-Minidiagramm dynamisch generiert. Wir beginnen mit einem sehr einfachen Beispiel, einem Kreis mit variabler Größe und Farbe.

    function render_circle(
      p_radius in number   default 16,
      p_color  in varchar2 default '#000000'
    ) return varchar2 is
      l_svg_xml varchar2(32767):=
        '<script type="image/svg+xml">'||
        '  <svg xmlns="http://www.w3.org/2000/svg" width="#SVGWIDTH#" height="#SVGHEIGHT#">' ||
        '    <circle r="#RADIUS#" cx="#CENTERX#" cy="#CENTERY#"  fill="#COLOR#" />'||
        '  </svg>'||
        '</script>';
    begin
      l_svg_xml := replace(l_svg_xml, '#SVGWIDTH#', to_char(round(p_radius*2)));
      l_svg_xml := replace(l_svg_xml, '#SVGHEIGHT#', to_char(round(p_radius*2)));
      l_svg_xml := replace(l_svg_xml, '#RADIUS#', to_char(round(p_radius)));
      l_svg_xml := replace(l_svg_xml, '#CENTERX#', to_char(round(p_radius)));
      l_svg_xml := replace(l_svg_xml, '#CENTERY#', to_char(round(p_radius)));
      l_svg_xml := replace(l_svg_xml, '#COLOR#', p_color);
      return l_svg_xml;
    end render_circle;
    

    In einem Bericht kann die Funktion nun wie folgt verwendet werden.

    select 
      ename, 
      empno, 
      sal, 
      html5_svg_apex.render_circle(
        p_radius => (sal / (select max(sal) from emp)) * 40,
        p_color  => 'red'
      ) sal_circle
    from emp
    

    Zunächst arbeitet der Bericht noch nicht ganz wie gewünscht (Abbildung 7) ...

    SVG-Anweisungen in einem APEX-Bericht - man sieht zunächst XML ...

    Abbildung 7: SVG-Anweisungen in einem APEX-Bericht - man sieht zunächst XML ..."

    Der Cross-Site-Skripting-Schutz ist standardmäßig aktiv - so wie es sein soll. Damit das SVG-XML nicht nur angezeigt, sondern interpretiert wird, müssen Sie die Berichtsspalte umstellen. Navigieren Sie zu den Berichtsattributen und dort zur Spalte SAL_CIRCLE . Stellen Sie dann, wie in Abbildung 8 dargestellt, die Darstellungsweise auf Standardberichtsspalte um.

    Berichtsspalte auf "Standardberichtsspalte" umstellen

    Abbildung 8: Berichtsspalte auf "Standardberichtsspalte" umstellen

    Nun wird das SVG korrekt angezeigt ...

    APEX-Bericht mit SVG-Elementen

    Abbildung 9: APEX-Bericht mit SVG-Elementen

    Nun können Sie noch weiter gehen. Spielen Sie mit dem folgenden Code das PL/SQL-Paket HTML5_SVG_OBJECTS ein; damit werden Ihnen neben Kreisen noch einige weitere Diagrammtypen angeboten. Natürlich steht es Ihnen frei, dieses Paket nach eigenen Bedürfnissen anzupassen oder zu erweitern ...

    create or replace package html5_svg_apex is
      function render_circle(
        p_radius in number   default 16,
        p_color  in varchar2 default '#000000'
      ) return varchar2;
    
      function render_gradient_status(
        p_worstvalue    in number,
        p_bestvalue     in number,
        p_value         in number,
        p_worstcolor    in varchar2 default 'rgb(255,0,0)',
        p_bestcolor     in varchar2 default 'rgb(0,255,0)',
        p_midcolor      in varchar2 default 'rgb(255,255,0)',
        p_width         in number default 50,
        p_height        in number default 16
      ) return varchar2;
    
      function render_triangle(
        p_width  in number   default 16,
        p_height in number   default 16,
        p_color  in varchar2 default '#000000',
        p_rotate in number   default 0
      ) return varchar2;
    
      function render_minibarchart(
        p_value1 in number,
        p_label1 in varchar2 default null,
        p_value2 in number default null,
        p_label2 in varchar2 default null,
        p_value3 in number default null,
        p_label3 in varchar2 default null,
        p_value4 in number default null,
        p_label4 in varchar2 default null,
        p_value5 in number default null,
        p_label5 in varchar2 default null,
        p_value6 in number default null,
        p_label6 in varchar2 default null,
        p_color1 in varchar2 default 'red',
        p_color2 in varchar2 default 'green',
        p_color3 in varchar2 default 'blue',
        p_color4 in varchar2 default 'yellow',
        p_color5 in varchar2 default 'magenta',
        p_color6 in varchar2 default 'brown',
        p_bar_width in number default 5,
        p_height in number default 32,
        p_max_value in number default null
      ) return varchar2;
    
    end html5_svg_apex;
    /
    sho err
    
    
    create or replace package body html5_svg_apex is
      C_SVGOPEN  varchar2(1000) := '<script type="image/svg+xml"><svg xmlns="http://www.w3.org/2000/svg" ';
      C_SVGCLOSE varchar2(1000) := '</svg></script>';
    
      function render_circle(
        p_radius in number   default 16,
        p_color  in varchar2 default '#000000'
      ) return varchar2 is
        l_svg_xml varchar2(32767):=
          '<script type="image/svg+xml">'||
          '  <svg xmlns="http://www.w3.org/2000/svg" width="#SVGWIDTH#" height="#SVGHEIGHT#">' ||
          '    <circle r="#RADIUS#" cx="#CENTERX#" cy="#CENTERY#"  fill="#COLOR#" />'||
          '  </svg>'||
          '</script>';
      begin
        l_svg_xml := replace(l_svg_xml, '#SVGWIDTH#', to_char(round(p_radius*2)));
        l_svg_xml := replace(l_svg_xml, '#SVGHEIGHT#', to_char(round(p_radius*2)));
        l_svg_xml := replace(l_svg_xml, '#RADIUS#', to_char(round(p_radius)));
        l_svg_xml := replace(l_svg_xml, '#CENTERX#', to_char(round(p_radius)));
        l_svg_xml := replace(l_svg_xml, '#CENTERY#', to_char(round(p_radius)));
        l_svg_xml := replace(l_svg_xml, '#COLOR#', p_color);
        return l_svg_xml;
      end render_circle;
    
      function render_gradient_status(
        p_worstvalue    in number,
        p_bestvalue     in number,
        p_value         in number,
        p_worstcolor    in varchar2 default 'rgb(255,0,0)',
        p_bestcolor     in varchar2 default 'rgb(0,255,0)',
        p_midcolor      in varchar2 default 'rgb(255,255,0)',
        p_width         in number default 50,
        p_height        in number default 16
      ) return varchar2 is 
        l_svg_xml varchar2(32767):=
          '<script type="image/svg+xml">'||
          ' <svg xmlns="http://www.w3.org/2000/svg" width="#SVGWIDTH#" height="#SVGHEIGHT#">' ||
          '  <defs>'||
          '   <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">'||
          '    <stop offset="0%"   style="stop-color:#WORSTCOLOR#;stop-opacity:1" />'||
          '    <stop offset="50%"  style="stop-color:#MIDCOLOR#;stop-opacity:1" />'||
          '    <stop offset="100%" style="stop-color:#BESTCOLOR#;stop-opacity:1" />'||
          '   </linearGradient>'||
          '  </defs>'||
          '  <rect x="0" y="0" width="#SVGWIDTH#" height="#SVGHEIGHT#" fill="url(#grad1)"/>'||
          '  <line x1="#VALUE_X#" y1="0" x2="#VALUE_X#" y2="#SVGHEIGHT#" stroke="black" stroke-width="2"/>'||
          ' </svg>'||
          '</script>';
      begin
        l_svg_xml := replace(l_svg_xml, '#SVGWIDTH#', to_char(round(p_width)));
        l_svg_xml := replace(l_svg_xml, '#SVGHEIGHT#', to_char(round(p_height)));
        l_svg_xml := replace(l_svg_xml, '#BESTCOLOR#', p_bestcolor);
        l_svg_xml := replace(l_svg_xml, '#MIDCOLOR#', p_midcolor);
        l_svg_xml := replace(l_svg_xml, '#WORSTCOLOR#', p_worstcolor);
        l_svg_xml := replace(l_svg_xml, '#VALUE_X#', round(p_value / (p_bestvalue - p_worstvalue) * p_width));
        return l_svg_xml;
      end render_gradient_status;
    
      function render_triangle(
        p_width  in number   default 16,
        p_height in number   default 16,
        p_color  in varchar2 default '#000000',
        p_rotate in number   default 0
      ) return varchar2 is 
        l_svg_xml varchar2(32767):=
          '<script type="image/svg+xml">'||
          '  <svg xmlns="http://www.w3.org/2000/svg" width="#SVGWIDTH#" height="#SVGHEIGHT#">' ||
          '   <g transform="rotate(#ROTATE#,#CENTERX#,#CENTERY#)">'||
          '    <polygon points="0,#SVGHEIGHT#,#SVGWIDTH#,#SVGHEIGHT#,#CENTERX#,0" fill="#COLOR#"/>'||
          '   </g>'||
          '  </svg>'||
          '</script>';
      begin
        l_svg_xml := replace(l_svg_xml, '#SVGWIDTH#', to_char(round(p_width)));
        l_svg_xml := replace(l_svg_xml, '#SVGHEIGHT#', to_char(round(p_height)));
        l_svg_xml := replace(l_svg_xml, '#CENTERX#', to_char(round(p_width/2)));
        l_svg_xml := replace(l_svg_xml, '#CENTERY#', to_char(round(p_height/2)));
        l_svg_xml := replace(l_svg_xml, '#ROTATE#', to_char(round(p_rotate)));
        l_svg_xml := replace(l_svg_xml, '#COLOR#', p_color);
        return l_svg_xml;
      end render_triangle;
    
      function render_minibarchart(
        p_value1 in number,
        p_label1 in varchar2 default null,
        p_value2 in number default null,
        p_label2 in varchar2 default null,
        p_value3 in number default null,
        p_label3 in varchar2 default null,
        p_value4 in number default null,
        p_label4 in varchar2 default null,
        p_value5 in number default null,
        p_label5 in varchar2 default null,
        p_value6 in number default null,
        p_label6 in varchar2 default null,
        p_color1 in varchar2 default 'red',
        p_color2 in varchar2 default 'green',
        p_color3 in varchar2 default 'blue',
        p_color4 in varchar2 default 'yellow',
        p_color5 in varchar2 default 'magenta',
        p_color6 in varchar2 default 'brown',
        p_bar_width in number default 5,
        p_height in number default 32,
        p_max_value in number default null
      ) return varchar2 is
        l_svg_xml varchar2(32767) :=
          '<script type="image/svg+xml">'||
          ' <svg xmlns="http://www.w3.org/2000/svg" width="#SVGWIDTH#" height="#SVGHEIGHT#">' ||
          '  <g transform="translate(0,#SVGHEIGHT#) scale(1,-1)">'||
          '   #BAR1##BAR2##BAR3##BAR4##BAR5##BAR6#'||
          '  </g>'||
          '  </svg>'||
          '</script>';
    
        l_bar_template varchar2(32767) :=
          '<rect x="#X#" y="0" width="#BARWIDTH#" height="#BARHEIGHT#" stroke="#000000" fill="#BARCOLOR#">'||
          ' <title>#BARLABEL#</title>'||
          '</rect>';
    
        v_max_value number := nvl(
          p_max_value, 
          greatest(
            nvl(p_value1, 0), nvl(p_value2, 0), nvl(p_value3, 0), 
            nvl(p_value4, 0), nvl(p_value5, 0), nvl(p_value6, 0)
          )
        );
        v_bar_cnt number := 
          (case when p_value1 is null then 0 else 1 end) + 
          (case when p_value2 is null then 0 else 1 end) + 
          (case when p_value3 is null then 0 else 1 end) + 
          (case when p_value4 is null then 0 else 1 end) + 
          (case when p_value5 is null then 0 else 1 end) + 
          (case when p_value6 is null then 0 else 1 end); 
    
        function get_single_bar(p_id number, p_value number, p_color varchar2, p_label varchar2)
        return varchar2 is 
          l_bar varchar2(32767) := null;
        begin
          if p_value is not null then 
            l_bar := l_bar_template;
            l_bar := replace(l_bar, '#X#', to_char(round(p_bar_width * p_id)));
            l_bar := replace(l_bar, '#BARWIDTH#', to_char(round(p_bar_width)));
            l_bar := replace(l_bar, '#BARHEIGHT#', to_char(round((p_value / v_max_value) * p_height)));
            l_bar := replace(l_bar, '#BARCOLOR#', p_color);
            l_bar := replace(l_bar, '#BARLABEL#', (p_label||': '||p_value));
          else
            l_bar := null;
          end if;
          return l_bar;
        end get_single_bar;
      begin
        l_svg_xml := replace(l_svg_xml, '#SVGWIDTH#', to_char(round(p_bar_width * (v_bar_cnt + 2))));
        l_svg_xml := replace(l_svg_xml, '#SVGHEIGHT#', to_char(round(p_height)));
        l_svg_xml := replace(l_svg_xml, '#BAR1#', get_single_bar(1, p_value1, p_color1, p_label1));
        l_svg_xml := replace(l_svg_xml, '#BAR2#', get_single_bar(2, p_value2, p_color2, p_label2));
        l_svg_xml := replace(l_svg_xml, '#BAR3#', get_single_bar(3, p_value3, p_color3, p_label3));
        l_svg_xml := replace(l_svg_xml, '#BAR4#', get_single_bar(4, p_value4, p_color4, p_label4));
        l_svg_xml := replace(l_svg_xml, '#BAR5#', get_single_bar(5, p_value5, p_color5, p_label5));
        l_svg_xml := replace(l_svg_xml, '#BAR6#', get_single_bar(6, p_value6, p_color6, p_label6));
        return l_svg_xml;
      end render_minibarchart;
    end html5_svg_apex;
    /
    sho err
    

    Die Nutzung des Pakets in einem APEX-Bericht könnte dann so aussehen. Erzeugen Sie einen neuen Bericht mit dieser SQL-Abfrage ...

    select 
      empno,
      ename,
      sal,
      comm,
      html5_svg_apex.render_gradient_status(
        p_worstvalue => 0,
        p_bestvalue  => (select max(sal) from emp),
        p_value      => sal,
        p_bestcolor  => '#00ff00',
        p_worstcolor => '#ff0000'
      ) as  sal_status,
      case
        when comm is null then null
        else 
          html5_svg_apex.render_circle(
            p_radius => comm/100, 
            p_color=>'orange'
          ) 
      end as comm_circle,
      html5_svg_apex.render_minibarchart(
        p_value1    => sal, 
        p_label1    => 'Sal',
        p_value2    => comm, 
        p_label2    => 'Comm',
        p_bar_width => 10, 
        p_height    => 50, 
        p_max_value =>( select max(sal) from emp) 
      ) sal_comm
    from emp
    

    Das Ergebnis sollte dann wie in Abbildung 10 aussehen ...

    Das Ergebnis: Ein APEX-Bericht mit SVG-Minidiagrammen

    Abbildung 10: Das Ergebnis: Ein APEX-Bericht mit SVG-Minidiagrammen

    SVG und HTML5 gehören mit Sicherheit zu den interessantesten Trends bei der Entwicklung von Web-Anwendungen, denn es werden völlig neue Möglichkeiten bei der Gestaltung der Nutzeroberflächen geschaffen. Auch die Einschränkungen, die sich bei Verwendung von Flash bei mobilen Endgeräten ergeben, treffen auf SVG und HTML5 nicht zu - die hier gezeigten Diagramme funktionieren auch auf gängigen mobilen Geräten, da SVG von diesen typischerweise unterstützt wird.

    Zurück zur Community-Seite