Skip to Content
Product Information
Author's profile photo Janet Nguyen

Performance Best Practices for SAP Analytics Cloud, Analytics Designer

Getting the most out of SAP Analytics Cloud doesn’t have to be a chore – here, you will find some best practices to keep Analytics Designer running smoothly. Some of these practices may be applicable universally in the application, while others are more catered towards specific situations. In any situation it is advised to test out these practices in a copy of your specific application prior to committing to any changes.

Analytics Designer and Stories both share the same widgets, such as tables, charts, and images. These practices will be useful for both portions of the application.

To get the most out of Analytics Designer, consider the following.

Application Design

Avoid one complex application containing many data sources.

  • Build multiple applications and use application URLs to navigate from one application to the other using the Navigation Utils API.
  • Try to divide one application into multiple tabs, panels, and so on, in order not to show each widget (and its data) of your analytic application at startup.
  • If you have multiple numeric chart widgets that consume the same data model, consider enabling the Query Merge capability (In the toolbar, select File > Edit Analytic Application > Query Settings, then enable Enable Query Merge) or using the getData API to display key figure values. This improves performance by avoiding multiple requests to the backend.

Application Start-Up Mode

Start the analytic application in embed mode (by adding ;mode=embed to the analytic application’s URL). This avoids loading and rendering the SAP Analytics Cloud shell. Expect a minor performance improvement here.

Avoid Duplicating Widgets Unnecessarily

  • Consider using the Set Style API to change the font, color, background color, and so on, of widgets. Contrast the following patterns:
    • Bad pattern (due to limitations in former releases): To display a text in two different colors, for example, red and green, you create two text widgets, each with a different text color. Depending on the situation, you show the appropriate text widget and hide the other one.
    • Good pattern: You create a single text widget and apply the appropriate color with setStyle().
  • Consider Moving Widgets

If your analytic application uses the same widget, for example, a table or chart, in several tab strips or panels, use the Move Widgets API to move this widget to the currently visible container, for example, a tab of a tab strip or a panel.

Load Invisible Widgets in Background

This provides a faster perceived startup of an analytic application containing (many) invisible widgets.

In former releases, all widgets (visible or invisible) had to be initialized before the analytic application started, that is, before the end user saw the startup screen.

As the application designer, you can change this default behavior by activating loading invisible widgets in the background. All widgets that are initially visible are initialized and displayed to the end user. After, the invisible widgets are initialized in the background to be available when the end user changes their visibility. Invisible widgets are not only widgets with the flag Show this item at view time turned off but also widgets inside invisible panels or on tabs in tab strips that are inactive at startup. This new loading behavior increases the perceived startup performance of the analytic application because the startup screen appears faster.

You can change to the new behavior by using the Analytic Application Settings dialog:

Then, select Load invisible widgets in the background:

You can overwrite this setting with the URL parameter loadInvisibleWidgets, This is useful, for example, if you as an application designer want to decide which mode is better working with your analytic application or if you as an end user aren’t satisfied with the choice of the application designer. There are two possible values:

The following value forces the “classic” default behavior and loads all widgets before any of the widgets are displayed to the end user:

loadInvisibleWidgets=onInitialization

The following value forces the background loading of invisible widgets after initially the visible widgets are displayed to the end user:

loadInvisibleWidgets=inBackground

Tips and Tricks for Loading Invisible Widgets in the Background

If the mode Load invisible widgets in background is used, it is possible that a script tries to access a widget which doesn’t exist at this point in time. This applies especially to the onInitialization event script. Here are some best practices to reveal the full potential of this optimization:

  • Leave the onInitialization event script empty.
  • If this isn’t possible, try to avoid accessing initially invisible widgets. Especially avoid using the setVariableValue() method if is not really necessary. If you need to set a variable initially to a static value, set the variable value state at design time in the analytic application or via a URL parameter instead.
  • If you need to configure invisible widgets via scripting (that includes setting filters or variables), then do this directly before the widget becomes visible (for example, before calling setVisible(true) or before changing the tab in a tab strip).
  • If you must access an invisible widget in the onInitialization event script, avoid nesting this widget too deep into a container structure, like, for example, in panels or tab strips. Ideally, the widget should be a direct child of the Canvas.
  • If you must access an invisible widget in the onInitialization event script, select the Always initialize on startup checkbox of the widget. It is located in the Styling Panel of the widget.
    Note: If the widget is part of an invisible container, then select also the Always initialize on startup checkbox of the container.
    Note: If an invisible panel has Always initialize on startup set to true, then it is initialized as if it was visible. In addition, its visible child widgets are also initialized.

Use the Pause Refresh Script API to Optimize Application runtime Performance

Application designer or developer can build applications, allowing analytic application end users to pause the refresh of a Table or Chart widget until they have completed adding or removing dimensions, filters, and key figures to such a widget. This optimizes runtime performance because an unnecessary initial refresh is avoided, and remote queries won’t be triggered every time when analytic application end users take an action.

Pause Refresh of a Chart and Table can be used in the following scenarios:

  • Pause the initial refresh of applications until they are modified during application onInit event to optimize the application start up performance. Especially if setVariableValue action is defined in onInitialization event, you can use pause refresh API to pause the initial rendering of charts and tables and refresh them after setVariableValue action is completed.
  • Pause the initial invisible widgets. Refresh the widgets after they start becoming visible. The initial rendering of invisible widgets can be skipped, and only visible widgets get rendered. This can speed up the initial application start-up performance.
  • Pause the refresh of chart and table after each click of user action. Users can take a couple of actions and then refresh the table/chat all at once.

To enable this capability, as an analytic application developer, you select Pause Data Refresh in the Builder Panel of the Table or Chart widget at design time.

Besides configuring the Table or Chart Builder Panel at design time, you, as an analytic application developer, can use the following scripting API to enable this capability at runtime as well:

Table.getDataSource().setRefreshPaused(paused: boolean);
Table.getDataSource().isRefreshPaused(): boolean;
Chart.getDataSource().setRefreshPaused(paused: boolean);
Chart.getDataSource().isRefreshPaused(): boolean;

You can also use the following API to enable and disable the refresh of several widgets at once:

Application.setRefreshPaused(dataSources: DataSource[], paused: boolean): void

When refresh is paused, end user interaction on a Table or Chart widget becomes meaningless. Most of the end user’s interaction will take no effect until refresh is resumed. Therefore, it is useful to enable or disable interactions on a widget. You, as an analytic application developer, can use the following scripting API to enable or disable user interactions on the Table or Chart widget:

Table.setEnabled(enabled: boolean);
Table.isEnabled(): Boolean
Chart.setEnabled(enabled: boolean);
Chart.isEnabled(): Boolean

Scripting

The following best practices apply to scripting in analytic applications:

Group Several setVariableValue() Calls Against BW Live Connections

If you set several variable values with the method setVariableValue() of a data source, write these commands in one direct sequence, one after the other, without any other script methods in between. This sequence is folded internally into a single backend call to submit variable values instead of multiple ones, thus improving application performance.

If you use many widgets based on one model, and they aren’t visible, at the same time, it can be better to use widget level variables to avoid implicit setting of variables on invisible widgets.

Prefer setDimensionFilter() over setVariableValue() with BW Live Connections

If a variable is affecting a dimension (for example, as a dynamic filter), consider using the method setDimensionFilter() of a data source instead of setVariableValue(). This avoids roundtrips to the BW backend server. However, variables are also used for other purposes than filtering. In such cases the usage of variables is still necessary.

Use getResultSet() Instead of getMembers()

The getResultSet method can use the resultset that is already available in the widget and does not need a roundtrip to the backend. The getMembers will always lead to an extra roundtrip to the backend system. So please consider using getResultSet if it is sufficient for your scenario.

If you need to use getMembers make sure to use the available options parameter to limit the list of returned members.

Avoid Changes in onResultChanged Event Script

Avoid changing the data source directly or indirectly in an onResultChanged event script. This might lead to an infinite loop.

Modifying Data Sources or Widgets at Application Startup

Configure the initial state of data sources or widgets (initial filter, feeding, variable values, and so on) at design time instead of using script methods during the onInitialization event. This event is executed after the widgets are initially loaded. Changing the state of the data source or a widget during this event results in another refresh, which may cause further roundtrips to the backend server.

If you need to modify data sources of widgets during the onInitialization event (by setting different filters or variable values), consider using the Pause Refresh API to pause the initial refresh of the application until it has been modified during the application’s onInitialization event. Proceed as follows: In the Builder panel of the widget’s data source, enable Pause Data Refresh. In the onInitialization event script, modify the widget’s data source. At the end of the script disable Pause refresh with setRefreshPaused(false).

Initialize Variables via URL Parameters

You can set (at the application level) variable values before the initial submit and the reception of the result sets by passing them as URL parameters of your analytic application’s URL. This is the same functionality a story provides. For each variable you specify the model, variable, and variable value with the parameters v<number>Model, v<number>Par, and v<number>Val, where <number> is a two-digit number.

For more details see the help page Variable Parameters.

Note: In the example URLs of this help page replace the fragment story with application.

Example:

In the following example, the variable Manager with value [“SM1″,”SM2”] of model view:[_SYS_BIC][t.TEST][remotejuice] is passed via URL (the URL isn’t functional out of the box as there are placeholders present for the tenant’s SAP Analytics Cloud URL, the tenant ID, and the application ID):

https://<TENANT>/sap/fpa/ui/tenants/<TENANT_ID>/bo/application/<APPLICATION_ID>?v01Model=view:[_SYS_BIC][t.TEST][remotejuice]&v01Par=Manager&v01Val=[“SM1″,”SM2”]

Avoid Repeating Instructions in Loops

Avoid repeating instructions in loops (for example, in for- or while-loops). Move these instructions before the loop. This applies also to instructions which seem to be cheap performance-wise, for example, Table_1.getDataSource().

Example:

// BAD
for (var i = 0; i < dimensions.length; i++) {
    Table_1.getDataSource().setDimensionFilter(dimensions[i], value);
}
// GOOD
var ds = Table_1.getDataSource();
for (var i = 0; i < dimensions.lenght; i++) {
    ds.setDimensionFilter(dimensions[i], value);
}

Prefer copyDimensionFilterFrom() over setDimensionFilter()

If you want to apply existing filters on several widgets, use the method copyDimensionFilterFrom() of a data source instead of setDimensionFilter().

Enable Planning on Tables Only when Planning is Used

If you add a table that is based on a panning model, then planning is enabled by default. If the table isn’t visible at startup or if the table isn’t used for planning at all, then disable planning. Use the method Table.getPlanning().setEnabled(true); of a data source to enable or disable planning, depending on whether you need planning or not, respectively.

Note that using the Pause Refresh capability is only possible if planning is disabled.

Use MemberInfo Object with setDimensionFilter()

If you use the method setDimensionFilter() of a data source and pass only a member ID, like in the following example, then a backend roundtrip is performed to fetch the member’s description:

var memberId = Dropdown_1.getSelectedKey();
Table_1.getDataSource().setDimensionFilter("sap.epm:Department", memberId);

When you pass a MemberInfo object (it contains a description) instead of only a member ID string, like in the following example, then no backend roundtrip is performed:

var memberId = Dropdown_1.getSelectedKey();
var memberDescription = Dropdown_1.getSelectedText();
Table_1.getDataSource().setDimensionFilter("sap.epm:Department", {id: memberId, description: memberDescription});

You can use this pattern also with an array of MemberInfo objects, like in the following example:

var resultSet = Table_2.getDataSource().getResultSet();
var memberInfos = ArrayUtils.create(Type.MemberInfo);
for (var i = 0; i < resultSet.length; i++) {
  var member = resultSet [i]["sap.epm:Department"];
  var memberId = member.id;
  var memberDescription = member.description;
  memberInfos.push({id: memberId, description: memberDescription});
}
Table_1.getDataSource().setDimensionFilter("sap.epm:Department", memberInfos);

Apply this pattern whenever the member description is available to you. If the filter definition is never visible to the end user of your analytics application, you can even use a dummy description.

Assigned tags

      1 Comment
      You must be Logged on to comment or reply to a post.
      Author's profile photo John Leggio
      John Leggio

      See this KBA: https://apps.support.sap.com/sap/support/knowledge/en/2511489 and blog as well: https://blogs.sap.com/2021/10/04/best-practices-for-performance/