Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
franois_henrotte
Active Contributor
Ten years ago I was asked to display some graphics containing only 5 values, but it was to be displayed as a nice curve. "MS Excel does it automatically, we want to have the same visual result in SAP".
Well, the requirement is easily expressed... but not easily achieved...

At those times we had already the GRAPH_MATRIX_2D function so I was able to display the 5 points. I just had to find a way to extrapolate a spline from those points. I came across the Catmull-Rom algorithm and found an implementation of it that I could translate into ABAP.

This algorithm is used mainly in the field of 3D rendering to have smooth curves.
My implementation works only on one dimension which is the height.

I give here a short program (sorry for the naming conventions but it is mainly intended to show the results of the algorithm) displaying some points and the interpolated curve. You can change the values in the SELECT-OPTIONS and see the curve related to the new values.

As usual, I used a docking container in order to avoid building a dynpro. This is also useful to keep all the code into a single program, so you can test it directly after a copy/paste.

Note: I don't use the old graphical functions anymore, as we have now the class cl_gui_chart_engine. This one is more powerful and there is no limit to 32 points. If you are not used to this class, the program is also a good way to learn about it.

Note2: on older SAP releases, you will have to concatenate the names of the points in place of using the variables in { }.
REPORT zzcurve.

TYPES: BEGIN OF ty_point,
y TYPE p DECIMALS 2,
END OF ty_point.

TYPES: BEGIN OF ty_dattab,
txt TYPE char20,
val TYPE p DECIMALS 2,
END OF ty_dattab.

DATA: w_point1 TYPE ty_point,
w_point2 TYPE ty_point,
w_point3 TYPE ty_point,
w_point4 TYPE ty_point,
w_new TYPE ty_point,
w_coeff TYPE p DECIMALS 2,
w_tabix TYPE i,
w_index TYPE i.

DATA: ws_dattab TYPE ty_dattab,
wt_dattab TYPE TABLE OF ty_dattab.

DATA: w_container TYPE REF TO cl_gui_docking_container.
DATA: w_graph TYPE REF TO cl_gui_chart_engine.
DATA: w_ixml TYPE REF TO if_ixml.
DATA: w_stream TYPE REF TO if_ixml_stream_factory.

DATA: w_repid TYPE repid,
w_dynnr TYPE dynnr.


SELECTION-SCREEN: BEGIN OF BLOCK b01 NO INTERVALS.
SELECT-OPTIONS: so_pnts FOR ws_dattab-val NO INTERVALS.
PARAMETERS: p_nb TYPE int4 DEFAULT 10.
SELECTION-SCREEN: END OF BLOCK b01,
SKIP.

PARAMETERS: p_lines TYPE c AS CHECKBOX USER-COMMAND lns.


INITIALIZATION.
so_pnts-low = 2222.
APPEND so_pnts.
so_pnts-low = 1111.
APPEND so_pnts.
so_pnts-low = 5432.
APPEND so_pnts.
so_pnts-low = 3333.
APPEND so_pnts.


AT SELECTION-SCREEN OUTPUT.
CHECK so_pnts[] IS NOT INITIAL.

IF w_container IS INITIAL.
* Create global objects
w_ixml = cl_ixml=>create( ).
w_stream = w_ixml->create_stream_factory( ).

w_repid = sy-repid.
w_dynnr = sy-dynnr.
CREATE OBJECT w_container
EXPORTING
repid = w_repid
dynnr = w_dynnr
side = w_container->dock_at_bottom
ratio = 80
EXCEPTIONS
OTHERS = 1.

CREATE OBJECT w_graph
EXPORTING
parent = w_container.
ENDIF.


PERFORM f_create_settings.

REFRESH: wt_dattab.

LOOP AT so_pnts.
w_tabix = sy-tabix.
w_index = sy-tabix + 1.

WRITE: / 'Point', sy-tabix, so_pnts-low.

CASE w_index.
WHEN 2.
CLEAR: w_point1, w_point2.
w_point3-y = so_pnts-low.
READ TABLE so_pnts INDEX w_index.
w_point4-y = so_pnts-low.
WHEN OTHERS.
w_point1 = w_point2.
w_point2 = w_point3.
w_point3-y = so_pnts-low.
READ TABLE so_pnts INDEX w_index.
IF sy-subrc = 0.
w_point4-y = so_pnts-low.
ENDIF.
ENDCASE.

DO.
ws_dattab-txt = |Point{ w_tabix }-{ sy-index }|.
w_coeff = sy-index * ( 1 / p_nb ).
IF w_coeff >= 1.
EXIT.
ENDIF.

PERFORM f_derive_point USING w_point1 w_point2 w_point3 w_point4
CHANGING w_new.

ws_dattab-val = w_new-y.
APPEND ws_dattab TO wt_dattab.
ENDDO.

ws_dattab-txt = |Point{ w_tabix }-O|.
ws_dattab-val = w_point3-y.
APPEND ws_dattab TO wt_dattab.

ENDLOOP.

PERFORM f_create_data USING wt_dattab.

CALL METHOD w_graph->render.


***
* FORMS
***
FORM f_derive_point USING i_p1 TYPE ty_point
i_p2 TYPE ty_point
i_p3 TYPE ty_point
i_p4 TYPE ty_point
CHANGING c_pn TYPE ty_point.
DATA: l_square TYPE f,
l_cube TYPE f.


l_square = w_coeff * w_coeff.
l_cube = l_square * w_coeff.

* c_pn-x = '0.5' * ( ( 2 * i_p2-x )
* + ( -1 * ( i_p1-x ) + i_p3-x ) * w_coeff
* + ( 2 * i_p1-x - 5 * i_p2-x + 4 * i_p3-x - i_p4-x ) * l_square
* + ( -1 * ( i_p1-x ) + 3 * i_p2-x - 3 * i_p3-x + i_p4-x ) * l_cube ).

c_pn-y = '0.5' * ( ( 2 * i_p2-y )
+ ( -1 * ( i_p1-y ) + i_p3-y ) * w_coeff
+ ( 2 * i_p1-y - 5 * i_p2-y + 4 * i_p3-y - i_p4-y ) * l_square
+ ( -1 * ( i_p1-y ) + 3 * i_p2-y - 3 * i_p3-y + i_p4-y ) * l_cube ).

ENDFORM.


FORM f_create_settings.
DATA: l_ixml_custom_doc TYPE REF TO if_ixml_document,
l_ostream TYPE REF TO if_ixml_ostream,
l_xstr TYPE xstring.

DATA: l_root TYPE REF TO if_ixml_element,
l_globalsettings TYPE REF TO if_ixml_element,
l_default TYPE REF TO if_ixml_element,
l_elements TYPE REF TO if_ixml_element,
l_chartelements TYPE REF TO if_ixml_element,
l_title TYPE REF TO if_ixml_element,
l_element TYPE REF TO if_ixml_element,
l_encoding TYPE REF TO if_ixml_encoding.


* Build the settings as an XML file
l_ixml_custom_doc = w_ixml->create_document( ).

l_encoding = w_ixml->create_encoding(
byte_order = if_ixml_encoding=>co_little_endian
character_set = 'utf-8' ).
l_ixml_custom_doc->set_encoding( l_encoding ).

l_root = l_ixml_custom_doc->create_simple_element(
name = 'SAPChartCustomizing' parent = l_ixml_custom_doc ).
l_root->set_attribute( name = 'version' value = '2.0' ). "1.1

l_globalsettings = l_ixml_custom_doc->create_simple_element(
name = 'GlobalSettings' parent = l_root ).

l_element = l_ixml_custom_doc->create_simple_element(
name = 'FileType' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( 'PNG' ).

IF p_lines IS INITIAL.
l_element = l_ixml_custom_doc->create_simple_element(
name = 'ChartType' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( 'Columns' ). "Lines, Pie
l_element = l_ixml_custom_doc->create_simple_element(
name = 'Dimension' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( 'Three' ). "PseudoThree
ELSE.
l_element = l_ixml_custom_doc->create_simple_element(
name = 'ChartType' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( 'Lines' ). "Columns, Pie
l_element = l_ixml_custom_doc->create_simple_element(
name = 'Dimension' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( 'Two' ).
ENDIF.

l_element = l_ixml_custom_doc->create_simple_element(
name = 'Width' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( '640' ).
l_element = l_ixml_custom_doc->create_simple_element(
name = 'Height' parent = l_globalsettings ).
l_element->if_ixml_node~set_value( '360' ).

l_default = l_ixml_custom_doc->create_simple_element(
name = 'Defaults' parent = l_globalsettings ).
l_element = l_ixml_custom_doc->create_simple_element(
name = 'FontFamily' parent = l_default ).
l_element->if_ixml_node~set_value( 'Arial' ).

l_elements = l_ixml_custom_doc->create_simple_element(
name = 'Elements' parent = l_root ).
l_chartelements = l_ixml_custom_doc->create_simple_element(
name = 'ChartElements' parent = l_elements ).
l_title = l_ixml_custom_doc->create_simple_element(
name = 'Title' parent = l_chartelements ).

l_element = l_ixml_custom_doc->create_simple_element(
name = 'Caption' parent = l_title ).
l_element->if_ixml_node~set_value( 'Smooth spline' ).

* Set the settings of Graphics
l_ostream = w_stream->create_ostream_xstring( l_xstr ).
CALL METHOD l_ixml_custom_doc->render EXPORTING ostream = l_ostream.
w_graph->set_customizing( xdata = l_xstr ).

ENDFORM.


FORM f_create_data USING it_data TYPE STANDARD TABLE.
DATA: l_ixml_data_doc TYPE REF TO if_ixml_document,
l_ostream TYPE REF TO if_ixml_ostream,
l_xstr TYPE xstring.

DATA: l_simplechartdata TYPE REF TO if_ixml_element,
l_categories TYPE REF TO if_ixml_element,
l_series TYPE REF TO if_ixml_element,
l_element TYPE REF TO if_ixml_element,
l_encoding TYPE REF TO if_ixml_encoding.

FIELD-SYMBOLS: <ls_data> TYPE any,
<lv_key> TYPE any,
<lv_val> TYPE any.


l_ixml_data_doc = w_ixml->create_document( ).

l_encoding = w_ixml->create_encoding(
byte_order = if_ixml_encoding=>co_little_endian
character_set = 'utf-8' ).
l_ixml_data_doc->set_encoding( l_encoding ).

l_simplechartdata = l_ixml_data_doc->create_simple_element(
name = 'SimpleChartData' parent = l_ixml_data_doc ).
l_categories = l_ixml_data_doc->create_simple_element(
name = 'Categories' parent = l_simplechartdata ).

LOOP AT it_data ASSIGNING <ls_data>.
ASSIGN COMPONENT 1 OF STRUCTURE <ls_data> TO <lv_key>.
l_element = l_ixml_data_doc->create_simple_element(
name = 'C' parent = l_categories ).
l_element->if_ixml_node~set_value( CONV string( <lv_key> ) ).
ENDLOOP.

l_series = l_ixml_data_doc->create_simple_element(
name = 'Series' parent = l_simplechartdata ).
l_series->set_attribute( name = 'label' value = 'Values' ).

LOOP AT it_data ASSIGNING <ls_data>.
ASSIGN COMPONENT 2 OF STRUCTURE <ls_data> TO <lv_val>.
l_element = l_ixml_data_doc->create_simple_element(
name = 'S' parent = l_series ).
l_element->if_ixml_node~set_value( CONV string( <lv_val> ) ).
ENDLOOP.

l_ostream = w_stream->create_ostream_xstring( l_xstr ).
CALL METHOD l_ixml_data_doc->render EXPORTING ostream = l_ostream.
w_graph->set_data( xdata = l_xstr ).

ENDFORM.
4 Comments