Saturday 7 January 2017

Do you know the Catmull-Rom algorithm ?

Well, the requirement is easily expressed… but not easily achieved…

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.

No comments:

Post a Comment