Zielsetzung
Es sollen in der S10 Anwendung Diagramme und Charts angezeigt werden. Die Daten sollen dynamisch aus dem SAP System gelesen werden.



Implentierung
Das S10 Framework bietet selbst keine Standardfunktionalität für die Anzeige von Diagrammen an. Es existiert jedoch eine große Anzahl an JavaScript Bibliotheken, die sehr einfach in eine S10 Anwendung eingebunden werden können. Entscheidend ist hierbei die Übergabe der Daten aus dem SAP System an die Bibliothek. Dies kann folgendermaßen geschehen:
 
Die benötigten Daten für das ein Diagramm werden in einer ABAP Methode besorgt und in einem S10 Feld gespeichert. Dieses Feld kann z.B. als verstecktes Eingabefeld in die S10 Anwendung, also im HTML Code, vorhanden sein:

<input class='input' type='hidden' name='diagram_01' id='diagram_01'/>

Die Daten werden dem Benutzer also nicht angezeigt, können aber am Frontend verarbeitet werden.
In einer ABAP Klasse existiert ein entsprechendes Feld, das heißt mit dem  gleichen Namen:


data: diagram_01 type string.

Eine ABAP Methode, die die Daten liest, könnte sein:

method build_diagram_01.

diagram_01 = 'Werte für das Diagramm z.B. Titel, Zahlenreihen, Labels etc..'

Endmethod.

Sobald die Daten in einer ABAP Methode gelesen wurden, stehen sie in der HTML Oberfläche zur Verfügung und können mit eine JavaScript Methode verarbeitet und an eine Bibliothek zur Generierung von Diagrammen übergeben werden:
function CreateDiagram()
{
	var values = document.getElementById('diagram_01').value;
	
	myDiagramLibrary.create(values);
}


Um Diagramme dynamisch neu generieren zu lassen, existieren zwei Mechanismen:
 
1. In einer ABAP Klasse können sog. Build-Methoden definiert werden, die aufgerufen werden, wenn sich andere Felder ändern.

Beispiel:
Ändert sich die vom Benutzer ausgewählte Kundennummer, werden die Daten für das Diagramm neu gelesen:
build_diagram_01
        importing
          kunnr      type kunnr
        exporting
          diagram_0 type string,


2. Für ein Eingabefeld kann im HTML Code ein onchange-Ereignis definiert werden, so dass bei Änderungen der Daten automatisch eine JavaScript Methode aufgerufen wird, um die Diagramme zu aktualisieren:
<input class='input' type='hidden' name='diagram_01' id='diagram_01'
    onchange='Update_diagram_01(this.value)'>

Die JavaScript Methode würde dann das Diagramm erstellen unter Verwendung einer bestimmten Bibliothek:
function update_diagram_01(v)
{
    // Create diagram
    // ...
       
}

Im einfachsten Fall würde man einfach eine Liste mit Zahlen übergeben, z.B. durch Semikolon getrennt. Denkbar sind aber auch andere Datenstrukturen, z.B. ein JSON-formatierter Text.
Beispiel
Im folgenden Beispiel werden zwei einfache Diagramme mit zufälligen Daten gefüllt, die in einer ABAP Methode gelesen werden. Sie können das Beispiel als Grundlage für eigene Implementierungen verwenden:




ABAP Programm: /s10/diagram_examples

program /s10/diagram_examples.

* diagram examples
class diagram_class definition inheriting from /s10/any.

  public section.

* project path (textpool, screens)
    constants:
        projectpath type string value 'sap/bc/bsp/s10/diagram'.


    data:
      diagram_01 type string,
      diagram_02 type string,
      kunnr      type kna1-kunnr,
      matnr      type mara-matnr.


    methods:

* Method to be run when screen 'start' becomes active
      on_init_start,

* generate some random date
      generate_random,

* Generate some data for diagram no. 1
      build_diagram_01
        importing
          kunnr      type kna1-kunnr
        exporting
          diagram_01 type string,

*Generate data for another diagram
      build_diagram_02
        importing
          matnr      type mara-matnr
        exporting
          diagram_02 type string.
endclass.

class diagram_class implementation.

  method on_init_start.

    if kunnr is initial.
      kunnr = 'K1032'.
    endif.

    if matnr is initial.
      matnr = '97'.
    endif.

  endmethod.

  method generate_random.

    data:
      o_rand type ref to cl_abap_random_int,
      seed   type i.

    " random number sequence.
    seed = cl_abap_random=>seed( ).
    cl_abap_random_int=>create(
    exporting
    seed =  conv i( sy-uzeit )
    min = 5
    max = 150
    receiving
    prng = o_rand
    ).

    diagram_01 =  'A;' && o_rand->get_next( ) && ';' && o_rand->get_next( ).
    diagram_01 = diagram_01 && '|B;' && o_rand->get_next( ) && ';' && o_rand->get_next( ).
    diagram_01 = diagram_01 && '|C;' && o_rand->get_next( ) && ';' && o_rand->get_next( ).
    diagram_01 = diagram_01 && '|D;' && o_rand->get_next( ) && ';' && o_rand->get_next( ).
    diagram_01 = diagram_01 && '|E;' && o_rand->get_next( ) && ';' && o_rand->get_next( ).

    diagram_02 =  'A;' && o_rand->get_next( ) .
    diagram_02 = diagram_02 && '|B;' && o_rand->get_next( ) .
    diagram_02 = diagram_02 && '|C;' && o_rand->get_next( ) .
    diagram_02 = diagram_02 && '|D;' && o_rand->get_next( ).


  endmethod.


  method build_diagram_01.

* Just for testing purposes: labe1;value1;value2|label2;value1;value2 etc.

    diagram_01 = 'A;1;2|B;2;4|C;3;6|D;4;8|E;5;10'.

  endmethod.

  method build_diagram_02.

    diagram_02 = 'A;25|B;25|C;40|D;10'.

  endmethod.


endclass.


* main class for this application
class main definition inheriting from /s10/any.

  public section.

    data:
      mandt type t000-mandt,  "client
      mtext type t000-mtext.  "client text

* We use another object of class diagram_class
    data:   mydiagram_class type ref to diagram_class.

    methods:
      logon.

endclass.

class main implementation.

* logon user
  method logon.

* set S10 license
    data: mydemolicense type string.
    mydemolicense = |Synactive GmbH demo license number=100|.
    mydemolicense = mydemolicense && | role=s10demo_role maxusers=10 signature=821.126.87.7|.
    s10setlicense( mydemolicense ).

* read client text and city from client table T000
    select single mandt mtext
       into (mandt,mtext)
         from t000
           where mandt = sy-mandt.


*  create our object
    create object mydiagram_class.

* show start-screen, mydiagram_class will become the active object
    mydiagram_class->s10nextscreen( 'start').


  endmethod.


endclass.

Der HTML View: diagram_class.start.html

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=400">
    <link rel='stylesheet' type='text/css' href='../../../style/s10.style.css'>
    <link rel='stylesheet' type='text/css' href='../../../style/custom.style.css'>

    <script src='../../synactiveS10/synactiveS10.java.js'></script>

    <!-- ChartJS is an open-source library for creating charts-->
    <script src='../../../scripts/Chart.js'></script>

    <!-- We can use predefined color-schemes for the colors for the diagrams-->
    <script src="../../../scripts/chartjs-plugin-colorschemes.js"></script>

    <title>Diagramme</title>

</head>

<body style="width: 100%; margin: 0px; padding: 0px;" onload='init();' class="colorscheme9">

    <div class="headerarea" style="width: 100%; padding: 10px;">
        <b>Beispiele für Diagramme mit S10</b>
    </div>

    <div class="toolbar">

        <button type="button" class="toolbarbutton" onclick="S10Apply('generate_random');">
            zufällige Daten generieren
        </button>

        <button type="button" class="toolbarbutton" style="float: right;" onclick="S10Logoff();">
            Abmelden
        </button>

    </div>

    <table class="tabstrip">
        <tr>
            <td id="tab_example_1" class="tabactive" onclick="S10ActivateTab(this);">
                Beispiel mit Balkendiagramm
            </td>
            <td id="tab_example_2" class="tab" onclick="S10ActivateTab(this);">

                Beispiel mit Kuchendiagramm

            </td>
    </table>

    <div class="tabpageactive" id="tab_example_1.area">

        <table style="width: 100%; padding: 15px; border-radius: 12px; background: #f1f5fb;">
            <tr>
                <td>
                    <span class="label">Kundennummer</span><br>
                    <input class='input' type='text' name='kunnr' id='kunnr' />
                    <span class="output" name="kunnr@text" id="kunnr_name"></span><br>

                    <!-- Hidden title for the diagram, put in via javascript later -->
                    <span style="display:none" id="chart-1-title">Diagramm für Kunde</span><br>

                    <!-- User can manpulate data in this field -->
                    <span class="label">Daten für Diagramm</span><br>
                    <input class='input' type='text' name='diagram_01' id='diagram_01' style="width:300px;"
                        onchange="updateChart(this.value,'chart-area-1','bar',
                    document.getElementById('chart-1-title').innerHTML + ' ' 
                    + document.getElementById('kunnr_name').innerHTML)" />                    
                    <br>
                    <!-- Hidden field, filled by ABAP method via S10 Framework , starts JavaScript on value change -->
                    <input class='input' type='hidden' name='diagram_01' id='diagram_01_hidden'
                        onchange="updateChart(this.value,'chart-area-1','bar',
                    document.getElementById('chart-1-title').innerHTML + ' ' 
                    + document.getElementById('kunnr_name').innerHTML)" /> <br>
                </td>
            </tr>
            <tr>
                <td>
                    <!-- Target area for the generated diagram-->
                    <canvas id="chart-area-1" style="width:100vw;height:50vh"></canvas>
                </td>
            </tr>
        </table>
    </div>

    <!-- Almost same coding, but other chart-type (pie)-->
    <div class="tabpage" id="tab_example_2.area">

        <table style="width: 100%; padding: 15px; border-radius: 12px; background: #f1f5fb;">
            <tr>
                <td>
                    <span class="label">Materialnummer</span><br>
                    <input class='input' type='text' name='matnr' id="matnr" /> <br>

                    <span style="display:none" id="chart-2-title">Kuchendiagramm für Material</span><br>

                    <span class="label">Daten für Diagramm</span><br>
                    <input class='input' type='text' name='diagram_02' id='diagram_02' style="width:300px;"
                        onchange="updateChart(this.value,'chart-area-2','pie',
                    document.getElementById('chart-2-title').innerHTML + ' ' 
                    + document.getElementById('matnr').value)" />                     
                    <br>

                    <input class='input' type='hidden' name='diagram_02' id='diagram_02_hidden' 
                    onchange="updateChart(this.value,'chart-area-2','pie',
                    document.getElementById('chart-2-title').innerHTML + ' ' 
                    + document.getElementById('matnr').value)" />
                    <br>
                </td>
            </tr>

            <tr>
                <td>
                    <canvas id="chart-area-2" style="width:100vw;height:50vh"></canvas>
                </td>
            </tr>
        </table>
    </div>

    <script>
        var myCharts = {};

        function updateChart(v, chartArea, barType, title) {

            // Extract values from string, format depends on own implementation
            // Here: label1;value1;value2|label2;value1;value2 ... 
            a = v.split("|");
            var mydata = [];
            var mydata2 = [];
            var mylabels = [];
            var dataset1 = [];
            var dataset2 = [];
            for (var i = 0; i < a.length; i++) {
                mylabels.push(a[i].split(";")[0]);
                mydata.push(a[i].split(";")[1]);
                if (a[i].split(";").length >= 3) {
                    mydata2.push(a[i].split(";")[2]);
                }
            }

            // Dataset for previous year
            dataset1 = {
                label: new Date().getFullYear() - 1,
                borderWidth: 1,
                data: mydata,
            };

            // Dataset for current year
            dataset2 = {
                label: new Date().getFullYear(),
                borderWidth: 1,
                data: mydata2,
            };
            var myChartData = {
                labels: mylabels,
                datasets: [
                    dataset1, dataset2
                ]
            };
            var ctx = document.getElementById(chartArea).getContext('2d');

            // Create chart-boject only one time and save it to an array
            if (typeof myCharts[chartArea] == 'undefined') {
                myCharts[chartArea] = new Chart(ctx, {
                    type: barType,
                    data: myChartData,
                    options: {
                        plugins: {
                            colorschemes: {
                                scheme: 'brewer.Paired12'
                            }
                        },
                        responsive: true,
                        legend: {
                            position: 'top',
                        },
                        title: {
                            display: true,
                            text: title
                        }
                    }
                });
            }
            else {
                myCharts[chartArea].data = myChartData;
                myCharts[chartArea].type = barType;
                myCharts[chartArea].options = {
                    plugins: {
                        colorschemes: {
                            scheme: 'brewer.Paired12'
                        }
                    },
                    responsive: true,
                    legend: {
                        position: 'top',
                    },
                    title: {
                        display: true,
                        text: title
                    }
                }
            }

            myCharts[chartArea].update();
        }
    </script>
</body>
</html>

Komponente S10 Framework