SAPAnalyticsCloud AnalyticsDesigner DeveloperHandbook
SAPAnalyticsCloud AnalyticsDesigner DeveloperHandbook
Developer Handbook
Table of Contents
Table of Contents.................................................................................................................... 1
Figures................................................................................................................................... 10
7 Planning................................................................................................................... 279
7.1 What to Expect from Analytics Designer Regarding Planning? ................................... 279
Table of Contents 7
8 Predictive................................................................................................................. 291
8.1 Time Series Forecast ................................................................................................ 291
8.1.1 Switch On and Off Forecast..................................................................................... 291
8.1.2 Configure Forecast .................................................................................................. 291
8.2 Smart Insights ........................................................................................................... 292
8.2.1 Discover per Selected Data Point ............................................................................ 292
8.3 Smart Grouping ......................................................................................................... 293
8.3.1 Switching Smart Grouping On and Off ..................................................................... 293
8.3.2 Configuring Smart Grouping .................................................................................... 294
8.4 Smart Discovery........................................................................................................ 294
8.5 Search To Insight ...................................................................................................... 295
10.1 Scenario 1: How You Can Embed an Analytic Application in a Host HTML Page via
iFrame ...................................................................................................................... 310
10.1.1 postMessage........................................................................................................... 310
10.1.2 onPostMessageReceived ........................................................................................ 311
10.1.3 Example .................................................................................................................. 311
10.2 Scenario 2: How You Embed a Web Application in an Analytic Application Through
the Web Page Widget ............................................................................................... 312
10.2.1 Web Page Widget-Related postMessage and onPostMessageReceived ................. 312
10.2.2 Case 1 – Posting Messages from the Host Analytic Application to the Embedded
Application .............................................................................................................. 312
10.2.3 Case 2 – Posting Messages from the Embedded Application to the Host Analytic
Application .............................................................................................................. 313
Figures
Figure 1: Bookmark Component in Outline .............................................................................. 19
Figure 2: Side Panel of Bookmark Component ........................................................................ 19
Figure 3: Turn on Translation .................................................................................................. 20
Figure 4: Create Application .................................................................................................... 22
Figure 5: Edit Sharing Settings ................................................................................................ 22
Figure 6: Open in View Mode .................................................................................................. 23
Figure 7: Run Analytic Application ........................................................................................... 24
Figure 8: Fullscreen ................................................................................................................ 24
Figure 9: Save & Leave Dialog ................................................................................................ 25
Figure 10: Outline ................................................................................................................... 26
Figure 11: Context Menu for Scripting Objects in Outline ......................................................... 27
Figure 12: Context Menu for Canvas Objects in Outline ........................................................... 27
Figure 13: Widget Name ......................................................................................................... 28
Figure 14: Analytics Designer Properties ................................................................................. 28
Figure 15: Dropdown Menu Style ............................................................................................ 28
Figure 16: Filter Menu Style .................................................................................................... 29
Figure 17: Visual Feedback of Mouse Click & Hover................................................................ 29
Figure 18: Settings of Icon....................................................................................................... 29
Figure 19: Type of Button ........................................................................................................ 29
Figure 20: Actions Menu ......................................................................................................... 30
Figure 21: Quick Menu Options in Styling Panel ...................................................................... 30
Figure 22: Create Calculation .................................................................................................. 33
Figure 23: Reference Script Variable ....................................................................................... 33
Figure 24: Edit Scripts ............................................................................................................. 34
Figure 25: Multiple Events ....................................................................................................... 34
Figure 26: Script for Dropdown ................................................................................................ 34
Figure 27: Script for Chart ....................................................................................................... 34
Figure 28: Hover Menu............................................................................................................ 35
Figure 29: Add Script Object ................................................................................................... 35
Figure 30: Add Script Function ................................................................................................ 35
Figure 31: Script Object Function ............................................................................................ 35
Figure 32: Script of Script Object Function............................................................................... 36
Figure 33: Script Editor............................................................................................................ 36
Figure 34: Areas of Script Editor.............................................................................................. 36
Figure 35: Accessing Objects .................................................................................................. 38
Figure 36 Gross Margin per Location Chart ............................................................................. 69
Figure 37 Newly Added Row Has Description "Sum", ID Is a UUID.......................................... 72
Figure 38 Totals Member Is Generated by Choosing Show Totals ........................................... 73
Figure 39 Comment Row or Comment Column Is Added to Table in Frontend ......................... 74
Figure 40 Input Parameter Is Not a Concrete Data Cell Selection ............................................ 77
Figure 41 Time Series Chart with Forecast Enabled ................................................................ 80
Figure 42 Bubble Chart with Smart Grouping Enabled ............................................................. 82
Figure 43 Get Dimension Attributes ......................................................................................... 84
Figure 44 Visible Properties .................................................................................................... 85
Figure 45 Identify a Table Cell and Return a Result Member ................................................... 87
Figure 46 Return "undefined" If More Than One Result Set Member Found ............................. 89
Figure 47 Attributes Displayed as ResultMemberInfo Properties .............................................. 90
Figures 11
Therefore, analytics designer is another way to create analytical content in SAP Analytics Cloud.
An analytic application typically contains some custom logic, expressed with the help of scripts.
With analytic applications there is much more flexibility to implement custom behavior. It requires
a higher skill level to create those.
From a consumption point of view, there shouldn't be any difference between stories and analytic
applications. The consumer shouldn't be aware of whether the analytical content is a story or an
analytic application. The exposed widgets, the available functionality, and the look, feel, and
behavior should be the same.
In addition, an analytic application has a dedicated lifecycle. You start it and there are certain
steps which are performed, like the startup event, for example. The story doesn't have that. You
can switch the story between edit and view mode as often as you like.
These are major differences. That is why we offer two artifacts and two corresponding design
time environments to create stories and analytic applications.
As a first step, you need to decide whether you want to visualize your data in a table or a chart
and add a table or a chart to your analytic application. This triggers another step for picking a
model. A model is a representation of the business data of an organization, organized into
dimensions and measures. In addition to widgets showing data, you add to the layout other
widgets that control data, such as filters, arrange and configure them, and wire them up.
Almost all widgets expose events. To add custom logic to the analytic application, you can
implement event handlers with the help of the scripting language.
About Analytics Designer 15
Example:
Let's say a dropdown is populated with the available years in the data model - 2015 to 2019. The
dropdown exposes the onSelect event, which is triggered whenever a value is selected from the
dropdown. The implementation of that event handler could read the selected value and set a filter
for the selected year in the model. The numbers shown reflects the selected year.
Because you can add many event handlers using the script API of all widgets and other service
APIs offered, the application can be geared towards the specific needs of a customer.
About Analytics Designer 16
Example:
Let's say that there is a script API method available for filtering members: setFilter("YEAR",
"2014"). A member is an element of a dimension. The plain JavaScript method expects two
strings, and this is what is executed at runtime by the web browser. However, our definition of the
script API method uses dedicated predefined types for our business domain, that is
setFilter(Dimension, Member). With that definition, the system checks and validates that the
passed strings are a valid dimension and a valid member.
The script editor uses the type information. It doesn't just statically check the types but uses the
knowledge about the underlying model and provides value help to offer dimensions and members
existing in the model.
Getting Started 17
2 Getting Started
Analytics designer provides a software development environment that enables analytic
application developers to reuse SAP Analytics Cloud widgets and other functionalities to build
different kinds of applications. Interactions between different widgets, pages, and applications are
implemented with script functionalities (including planning, machine learning, and so on) -
at design time. End users will then be consuming these applications - at runtime.
Analytics designer is built around core story components to keep them synchronized as you go
forward. Analytics designer and story have different entry points but share much in common:
• Analytics designer is deeply integrated into SAP Analytics Cloud.
• Analytics designer and story share data connectivity and User Interface artifacts.
• It ensures a consistent user experience for application and story consumers.
• It inherits infrastructure and lifecycle management of SAP Analytics Cloud.
2.1 Prerequisites
Full access: the application author who creates or edits the application needs a Create, Read,
Update and Delete access (CRUD). The CRUD permissions are part the standard role
Application Creator or can be assigned to any other role.
The folder where the application is stored passes on its access settings. For example, when an
application is saved in a public folder, all users get Read access by default.
The ability to create, update, and delete is part of an extra standard role Application Creator.
2.1.4 Modes
There are three modes in analytic applications:
• Edit mode
This is a design time mode. It allows you to edit applications. CRUD access is
necessary. The application opens in edit mode by default if you have CRUD access.
• Present mode
This is a runtime mode. It allows you to execute applications. Read access is necessary.
The application opens in present mode by default if you run an it from edit mode.
• View mode
This is a runtime mode. It allows you to execute applications. Read access is necessary.
The application opens in view mode by default if you have read access.
2.2.1 Canvas
The Canvas is a flexible space where you can explore and present your data. Applications have
only one Canvas. Scripting allows you to build numerous navigation options in your app.
Note: Applications don’t have pages. The story concepts of cascading story, page, widget filters,
and input controls are thus unavailable in applications. You should add a Filter line widget
instead. The Filter line widget mimics the story filter and can be placed on the application Canvas.
Assign a data-bound source widget, such as a table or a chart, as source widget. Target widgets
can be assigned via scripting to apply the selected filters to several widgets.
To learn more about widgets, see chapter Widget Concepts, Script APIs, and Usage.
Data Sources can be based on a variety of models provided by, for example:
• SAP HANA
• SAP BW
• Data Warehouse Cloud Analytical Data Set
Getting Started 19
Note: Custom widgets that are used in an analytic application are also exported with the analytic
application.
Note: The software release Wave versions of SAP Analytics Cloud installed on the source and
target tenants need to be either the same Wave version or just one Wave version different.
Save Bookmark
Write analytic designer scripts to save a bookmark. At runtime, the analytic application user can
capture the latest application state via a script API:
BookmarkSet_1.save("application bookmark", true, true);
Certain information concerning a bookmark can be retrieved via a script API as well:
BookmarkSet_1.getAll(); //get all valid bookmarks
BookmarkSet_1.getVersion(); //get the version of current bookmark
Delete Bookmark
To turn on translation of an analytic application for the first time, the application developer must
open the Analytic Application Details dialog and switch on Mark for translation.
The current language will become the source language of this document. If users switch to
another language, the document will be shown only in view mode.
To export the widget’s content, select the menu item Export. This opens the Export dialog. You
can choose with the File Type dropdown the output format (table: CSV, Excel, PDF, chart: CSV).
When you choose PDF, you can also configure paper orientation, paper size, and so on:
There is also an extensive Export script API available. For more information see section Export
API.
Designing an Analytic Application 22
The default access set for an application saved in a public folder is read only for others. You need
to explicitly share your application with other users and give CRUD access to allow them to edit
the application.
At design time, the CRUD permissions are necessary, at runtime only read access. When users
have only read access and open an application from file repository, the application will open
automatically in runtime mode. If a user has CRUD permissions, the application will open per
default in design time mode. If you as application author with CRUD permissions want to open
the application from file repository directly in view mode, you can select this option from context
menu when hovering over the application name in the list. If you are not the owner of the
application and it was not shared with full access, the application will open in view mode and you
don’t have the option in the context menu. Only for your own applications you have this option.
In edit mode, the URL contains mode=edit. In present mode, the URL contains mode=present. In
view mode, the URL contains mode=view. In embed mode, the URL contains mode=embed. An
analytical application opened in embed mode hides the toolbar.
Designing an Analytic Application 24
The analytic application opens in present mode by default when running the application from the
design time.
Figure 8: Fullscreen
• All visible widgets in the Layout area, either directly on the main Canvas or in a Popup
• The non-visible elements of an application in the Scripting area
Click on + to create Script Variables, Script Objects, OData Services, and Predictive Models. You
can maintain them here and use them in every script of the application.
The Outline has a search bar that filters the complete tree to match your search. Click the symbol
> to expand or collapse an item.
The widgets in the Outline, on the Canvas, and the side panel are always synchronized and based
on your selection. Widgets in the Outline have a context menu containing Rename, Find
Reference, Delete, and Hide. Hide conceals the widget on the Canvas in edit mode. It has no
influence on the different view modes when executing the application.
Widgets have their own analytic application Properties section in the Styling panel. This is where
the widget name used for scripting can be changed; it is updated in the Outline, and vice versa.
The specific properties of the analytics designer depend on the widget type.
Designing an Analytic Application 28
Dropdown Widget
Users can now configure dropdown style with greater granularity. In addition to the default style,
users can now configure different styles of dropdown menu when item are selected, or mouse
hover, or mouse down.
In addition to the default style, users can now configure different styles of filter menu during mouse
hover or mouse down.
Button Widget
Several new settings of Button widget have been added in the Styling panel:
The possible types of button are: standard, lite, emphasized, positive (accept), and negative
(reject).
Under Actions, you can flag the option to hide the widget in application view time.
Designing an Analytic Application 30
At runtime for each widget, there are quick menus for either a widget or relevant data points (that
is, Table or Chart). An application developer can configure the visibility of these quick menu items
via the settings in the Styling panel of a widget. More styling options are available.
By checking or unchecking the checkbox before each item, the application developer can control
the availability of the related quick menu item at runtime.
Most modern analytics tools avoid scripting to simplify the designer’s tasks. Users may find it
easier to use at first, but they quickly find themselves limited to the scenarios built into the tool.
Scripting allows you to go beyond present narratives, to respond to user interaction in a custom
way, to change data result sets, and to dynamically alter layout. Scripting frees your creativity.
For example, when calling a script API method on a Business Warehouse DataSource, the code
completion can propose measures as code completion options or values to specify a filter.
4.2.3 Events
Scripts always run in response to something happening in the application. Application events are
your hook. There are several types of events in analytic applications. Some occur in the
application itself and some occur on individual widgets.
Designers have access to this information and to the event’s two input parameters:
• origin: it is the domain of the host application. The contents of an iFrame don’t need to
be in the same origin as the host app, even when same origin policies are in effect. It
can be convenient but be careful about clickjacking attacks and malicious iFrame hosts.
For the sake of security, we recommend that you check this parameter to ensure that the
iFrame host is what you expect.
• message: it is the standard message parameter of the JavaScript PostMessage passed
into SAP Analytics Cloud. It does not follow any format and could be almost anything. It
is encoded using the structured clone algorithm and there are a few documented
limitations in what can and can’t be encoded.
For example, you, as an analytic application developer, can bind a calculated measure which
references one script variable (ScriptVariable_Rate) to a chart.
Scripting in Analytics Designer 33
Find them by hovering over the widget name in the Outline, or as a menu entry in the quick action
menu of each widget. The icon indicates the event. By clicking on it, the script editor opens the
selected function.
If a widget has multiple available events, you are presented with a choice in the hover menu.
If there is an event with an attached script, you can see the icon in the Outline. If there are no
attached script, there is no visible icon. In the following figure, the onSelect event of Dropdown_1
has a script, but there are no scripts attached to Chart_1.
If a widget has multiple events and at least one has a script attached, then the icon will be
displayed.
The hover menu will show which of the events have attached scripts.
Scripting in Analytics Designer 35
Within a script object, you can add several functions, by invoking Add Script Function in the
context menu. Keep in mind that the script object container is an organizational aid for you.
Individual functions are nested within global script objects. For example, in the figure below Error! R
eference source not found. you see the function1 nested within a script object called
ScriptObject_1.
Like Canvas widgets, the scripts attached to a function are created by clicking the icon in the
hover menu of that function. Functions that have and don’t have scripts are visible in the Outline,
just as with widgets.
Once you have a script attached to a function, you can call it whenever you please, from any
other script. The script objects are accessible by name and individual functions are accessible
within the objects. If you wanted to invoke the function1 script within ScriptObject_1, you would
call is like this:
ScriptObject_1.function1();
Write script in the main body using the inbuild help features like code completion and value help.
Scripting in Analytics Designer 37
Find a list of keyboard shortcuts in the help page “Using Keyboard Shortcuts in the Script Editor”:
https://help.sap.com/doc/00f68c2e08b941f081002fd3691d86a7/release/en-
US/68dfa2fd057c4d13ad2772825e83b491.html.
Find all places where a widget or a scripting object is used with the Find References feature. You
can find it in the context menu per object in the Outline. The result is displayed in the Reference
list tab of the Info panel.
You can change the name of a widget, gadget, script variable, script object, or script object
function by selecting it in the Outline, clicking the More button, selecting Rename, and entering a
new name.
You can change the name of a widget or gadget by selecting it in the Outline, then entering in the
Styling panel a new name in the Name input field.
You can change the name of a script variable, script object, or script object function by selecting
it in the Outline, entering in the Styling panel a new name in the Name input field, then clicking
button Done.
You can change the name of a script object function argument by selecting the script object
function in the Outline, clicking the Edit button of the function argument in the Styling panel,
entering a new name in the Name input field, then clicking button Done.
4.4.1 Typing
Normal JavaScript is weakly typed and dynamically typed. Weak typing means that the script
writer can implicitly coerce variables to act like different types. For example, you could have an
integer value and treat it as if it were a string. Dynamic typing means that the runtime will try to
guess the type from the context at that moment and the user can even change the type after the
variable is already in use. For example, you could change the value of the beforementioned
integer to another type of object at will; “Dear integer, you are now a duck”.
Scripting in Analytics Designer 38
Analytics designer forbids both. Once you have a duck, it remains a duck and you can’t recycle
variable names as new types. If you want something else, you’ll need another variable. It is also
strongly typed, meaning that if you want to use an integer as a string, you’ll have to explicitly cast
it. Both are a consequence of enabling the rich code completion capabilities in the editing
environment.
The analytics designer scripting language is still JavaScript. You can write perfectly valid
JavaScript while treating the language as if it was strongly and statically typed.
Fuzzy matching helps you finding the result even if you have made a typo or the written letters
are in the middle of the function. Fuzzy matching is applied automatically for the standard code
completion (for example, "cose" → "console").
The script validation runs automatically in the background and shows errors and warnings
indicated with red and orange underlying and a red or orange marker before the line number.
The available set of standard JavaScript built-in objects and their functionality is listed in the SAP
Analytics Cloud, analytics designer API Reference.
4.4.7 Loops
Two types of JavaScript loops are possible in analytics designer, for and while loops. Other
types, such as foreach iterators, are not supported.
SAP Analytics Cloud, analytics designer has no automatic type casting. It supports
• Triple equals
• Double equals only if both sides have the same static type
The examples below show the difference between double and triple equals operators. In both
cases, there is a variable aNumber, with an integer value and we are comparing it to the string "1".
In the double equals case, aNumber is cast to string and compared. The result is true, and the if
block is entered. In the triple equals case, aNumber is not cast to string and the comparison is
false, because the values are of a different type.
...
}
When performing the above console print on one of the events of Chart_1 itself, use the following
variation of the code:
var theDataSource = this.getDataSource();
console.log(theDataSource.getVariables());
Note: Analytics designer supports debugging analytics designer scripts in the Chrome browser
only.
Note: Analytics designer transforms the analytics designer scripts before they are run in the
browser. Thus, they will not look exactly like the script you wrote in the script editor of analytics
designer.
Note: To find the analytics designer script in the browser’s development tools, the script needs to
be run at least once during the current session.
Analytics designer script names follow a specific naming convention: All scripts of an application
are grouped in a
folder <APPLICATION_NAME>. Each script is named
<WIDGET_NAME>.<FUNCTION_NAME>.js.
For example: If an application My Demo Application contains a button Button_1 with an onClick
event script, then the name of the script is Button_1.onClick.js, which is in folder
My_Demo_Application.
Note: Special characters, for example space characters in the application name are replaced by
an underscore (_), except minus (-) and dot (.) characters, which remain unchanged.
You can quickly find a script by its name with the following steps:
• Press F12 to open the browser’s development tools.
• Press CTRL+P, then start typing a part of the script’s name.
You can also find a script in the file tree on the left side of the development tools using the
following steps:
• Press F12 to open the browser’s development tools.
Scripting in Analytics Designer 43
To pause a script while it is being executed, you can set breakpoints at certain locations in the
analytic designer script.
To set a breakpoint, open the script you want to pause during its execution and click on the line
number on the left side of the opened script in the developer tools.
A blue marker appears, highlighting the clicked line number. It indicates where the script will be
paused when it is being executed the next time. You can add several breakpoints in one script to
pause its execution at different points in time.
Scripting in Analytics Designer 44
To remove a breakpoint, click on the blue marker. The blue maker disappears, and the script's
execution won't stop at this point when the script is run the next time.
Analytic designer supports more debugging features by running an analytic application in debug
mode.
You enable the debug mode for an analytic application by appending the string ;debug=true to
the URL of the analytic application in the browser.
Note: The analytic designer script names in the browser change when the analytic application is
run in debug mode. In debug mode, the suffix -dbg is added to the script name, for example,
Button_1.onClick-dbg.js.
You enable the debug mode for an analytic application by appending the string ;debug=true to
the URL of the analytic application in the browser.
Note: The analytic designer script names in the browser change when the analytic application is
run in debug mode. In debug mode, the suffix -dbg is added to the script name, for example,
Button_1.onClick-dbg.js.
When the debug mode is enabled, you can pause an analytics designer script at a specific
location while it is being executed by placing a debugger; statement at this location of the script.
The difference to a regular breakpoint is that you can define the location where the script is paused
already while writing the script itself, that is, before running it.
Scripting in Analytics Designer 45
When the debug mode is enabled, then comments in a script are preserved in the transformed
script that is executed in the browser. This makes it easier to recognize specifically commented
locations in a script when its execution in the browser is paused.
Creating 2D-Arrays
Recall that you can easily create a one-dimensional (1D) array in a script. The following snippet,
for example, creates a 1D-array of numbers:
var arr1D = ArrayUtils.create(Type.number);
However, there is no similar method to create a 2D-array. Still it is possible to create a 2D-array.
The trick is to use a 1D-array that in turn contains several 1D-arrays. Together they act as a 2D-
array.
Example:
In the following example, let’s create a 2D-array of 4 columns and 3 rows that contains values of
type number.
First, let’s define the number of rows and columns as constants, this makes the following code
clearer:
var numCols = 4;
var numRows = 3;
As mentioned before, we can't directly create a 2D-array, but we can use a trick: We create a 1D-
array arr2D containing several 1D-arrays of type number. The number of 1D-arrays that arr2D
Scripting in Analytics Designer 46
must contain is numRows, the number of rows of our array. The completed construct will have the
same effect as a 2D-array.
Then we add the remaining 1D-arrays to arr2D. Each of them contains one more row of numbers.
Note that the for-loop below doesn’t start with 0 but with 1 because arr2D was already initialized
with arr1D, the 1D-array to contain the first row of numbers.
for (var i = 1; i < numRows; i++) {
arr2D.push(ArrayUtils.create(Type.number));
}
We're done!
Now we can set a value into the array arr2D in the form
arr2D[row][col] = value;
For example, let's set the increasing value of a counter to each of the elements of the 2D-array
arr2D with the following script:
var count = 0;
for (var row = 0; row < numRows; row++) {
for (var col = 0; col < numCols; col++) {
arr2D[row][col] = count;
count = count + 1;
}
}
Finally, let's get and output the values from the 2D-array arr2D with the following script:
for (row = 0; row < numRows; row++) {
for (col = 0; col < numCols; col++) {
Scripting in Analytics Designer 47
console.log(arr2D[row][col]);
}
console.log("");
}
The output is:
0
1
2
3
4
5
6
7
8
9
10
11
Example:
Let’s say you want to print the variables on Chart_1 to the console.
Get the data source on a widget with its getDataSource() function. This returns the data source
attached to that widget and allows you to perform further operations.
The snippet below prints the data source variables of Chart_1 to the console:
var theDataSource = Chart_1.getDataSource();
var theVariables = theDataSource.getVariables();
console.log(theVariables);
Example:
The following single filter value is set for dimension "EMPLOYEE_ID". This keeps only the member
with employee ID 230 in the drill-down:
Scripting in Analytics Designer 48
Example:
The following single but excluding filter value is set for dimension "EMPLOYEE_ID". This removes
the member with employee ID 230 from the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {value: "230", exclude: true});
Example:
The following multiple filter values are set for dimension "EMPLOYEE_ID". This keeps the members
with employee IDs 230 and 240 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {values: ["230", "240"]});
Example:
The following multiple but excluding filter values are set for dimension "EMPLOYEE_ID". This
removes the members with employee IDs 230 and 240 from the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {values: ["230", "240"], exclude: true});
Example:
The following range filter applied to dimension "EMPLOYEE_ID" keeps the members with employee
IDs between 230 and 240 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {from: "230", to: "240"});
Example:
The following range filter applied to dimension "EMPLOYEE_ID" keeps the members with employee
IDs less than 230 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {less: "230"});
Example:
The following range filter applied to dimension "EMPLOYEE_ID" keeps the members with employee
IDs less or equal than 230 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {lessOrEqual: "230"});
Example:
The following range filter applied to dimension "EMPLOYEE_ID" keeps the members with employee
IDs greater or equal than 230 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {greaterOrEqual: "230"});
Scripting in Analytics Designer 49
Example:
The following range filter applied to dimension "EMPLOYEE_ID" keeps the members with employee
IDs greater than 230 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", {greater: "230"});
You can also apply multiple range filters at once.
Example:
The following range filter applied to dimension "EMPLOYEE_ID" keeps the members with employee
IDs less than 230 and greater than 240 in the drill-down:
DS_1.setDimensionFilter("EMPLOYEE_ID", [{less: "230"}, {greater: "240"}]);
Example:
In the following example, the dimension filter values of the filter COUNTRY are retrieved:
var values = Table_1.getDataSource().getDimensionFilters("COUNTRY");
Each value in the array is an instance of either SingleFilterValue, MultipleFilterValue, or
RangeFilterValue, which all inherit from FilterValue. To work with such an instance, that is, to
access its type-specific properties, cast the instance to the corresponding type first, using the
type property. The following example shows how:
var value = Table_1.getDataSource().getDimensionFilters("COUNTRY")[0];
switch (value.type) {
case FilterValueType.Single:
var singleValue = cast(Type.SingleFilterValue, value);
console.log(singleValue.value); // you can access the 'value' property now
break;
case FilterValueType.Multiple:
var multipleValue = cast(Type.MultipleFilterValue, value);
console.log(multipleValue.values); // you can access the 'values' property now
break;
case FilterValueType.Range:
var rangeValue = cast(Type.RangeFilterValue, value);
console.log(rangeValue.from); // you can access the 'from' property now
console.log(rangeValue.to); // you can access the 'to' property now
// further range properties: 'less', 'lessOrEqual', 'greater', 'greaterOrEqual'
break;
default:
break;
}
Note: Currently this script API does not return time range filters.
Scripting in Analytics Designer 50
Note: In SAP BW backend systems you can create valid filters that are not yet supported by SAP
Analytics Cloud. As this script API implementation is based on SAP Analytics Cloud, it supports
the capabilities of SAP Analytics Cloud.
getDimensionProperties
getDimensionProperties(dimension: string | DimensionInfo): DimensionPropertyInfo[]
Returns all available dimension properties of the specified dimension of a data source.
See also section Dimension Properties on the Dimension Properties script API of the Table.
4.5.4 Hierarchies
You can set the drill level of a dimension hierarchy as well as expand or collapse individual
hierarchy nodes.
Note: Currently, this is supported only by data sources of Table and Chart widgets.
Specify the dimension and hierarchy level that you would like to set or retrieve:
DataSource.setHierarchyLevel(dimension: string|DimensionInfo, level?: integer):
void
DataSource.getHierarchyLevel(dimension: string|DimensionInfo): integer
Specify the dimension and hierarchy node that you would like to expand or collapse:
DataSource.expandNode(dimension: string|DimensionInfo, selection: Selection): void
DataSource.collapseNode(dimension: string|DimensionInfo, selection: Selection):
void
});
Example:
Let’s assume the current active hierarchy of dimension "Location_4nm2e04531" is set to a flat
presentation, then
Table_1.getDataSource().getMember("Location_4nm2e04531", "CT1");
returns the member info object
{id: 'CT1', description: 'Los Angeles', dimensionId: 'Location_4nm2e04531',
displayId: 'CT1'}
Note: The required member ID of a member may differ, depending on the current active hierarchy
of the member’s dimension. For example, for a HANA system, the member ID of the same
member may be "CT1" for the dimension "Location_4nm2e04531" with a flat presentation
hierarchy and "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]" for an actual
hierarchy. This influences the results of method getMember():
Scripting in Analytics Designer 52
Example:
Let’s assume that the current active hierarchy of dimension "Location_4nm2e04531" is set to
"State_47acc246_4m5x6u3k6s", a hierarchy of US-American states. Then
Table_1.getDataSource().getMember("Location_4nm2e04531", "CT1");
returns undefined, whereas
Table_1.getDataSource().getMember("Location_4nm2e04531",
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]");
returns the member info object
{id: '[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]', description: 'Los
Angeles', dimensionId: 'Location_4nm2e04531', ...}
Example:
Let’s assume that the current active hierarchy of "Location_4nm2e04531" is set to a flat
presentation. Then
Table_1.getDataSource().getMember("Location_4nm2e04531", "CT1");
returns the member info object
{id: 'CT1', description: 'Los Angeles', dimensionId: 'Location_4nm2e04531', ...}
whereas
Table_1.getDataSource().getMember("Location_4nm2e04531",
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]");
returns undefined.
Note: If the data source is associated with an R visualization and you specify a hierarchy other
than Alias.FlatHierarchy (flat presentation), then undefined is returned.
Examples:
Let’s assume that the current active hierarchy of "Location_4nm2e04531" is set to a flat
presentation. Then
Scripting in Analytics Designer 53
Table_1.getDataSource().getMembers("Location_4nm2e04531", 3);
returns the following array of member info objects:
[{id: 'CT1', description: 'Los Angeles', dimensionId: 'Location_4nm2e04531', ...},
{id: 'CT10', description: 'Reno', dimensionId: 'Location_4nm2e04531', ...},
{id: 'CT11', description: 'Henderson', dimensionId: 'Location_4nm2e04531', ...}]
You can achieve the same result with member options, specifying the limit parameter:
Table_1.getDataSource().getMembers("Location_4nm2e04531", {limit: 3);
The following example retrieves the first two members and specifies a hierarchy of US-American
states:
Table_1.getDataSource().getMembers("Location_4nm2e04531", {limit: 2, hierarchy:
"State_47acc246_4m5x6u3k6s");
This returns the result:
[{id: '[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]', description:
'California', dimensionId: 'Location_4nm2e04531', ...},
{id: '[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]', description: 'Los
Angeles', dimensionId: 'Location_4nm2e04531', ...}]
In addition, you can specify the access mode, that is, whether the returned data is from master
data (the default) or booked values.
Tip: To find out the booked values of a dimension (in combination with a specific hierarchy), create
an analytic application with a table that contains a data source with this dimension. Set the same
hierarchy to the dimension. In the Builder panel of the table, open the … (“three dots”) menu of
the dimension, then unselect the menu entry Unbooked Values. The table now displays the
booked values of the dimension (in combination with the selected hierarchy).
Scripting in Analytics Designer 54
Note: If the hierarchy specified in the members options doesn't exist, then an empty array is
returned.
Note: If the data source is associated with an R visualization and you specify a hierarchy other
than Alias.FlatHierarchy (flat presentation) in the members options, then an empty array is
returned.
4.6.3 Scripting
If you have successfully created a pattern-based function and its pattern, you can use it in every
script of the application just like any other script object function. The following example shows
how the pattern-based function is used in a script of the application:
var firstNameandName = ScriptObject_1.myPatternBasedFunction("joe.doe@sap.com");
Scripting in Analytics Designer 55
In this example, you want to swap the day with the month and only take the last two digits of the
year. But since this is ambiguous (it’s not clear if the number 11 at the end is taken from the month
or from the last digits of the year) you need to provide a second training example like 09.05.2020
as an input example and 05.09.20 as an output example.
So, you type John Doe has an appointment on 06.07.20 at 3:00pm. as input and Name: John
Doe, Date: 06.07.20, Time: 3:00pm as output in the Training Example section.
Suppose you were only logging the variables in the above example as a debug aid. You were not
re-using them, and the multiple lines were visual clutter. Then you might want to use method
chaining. The code below uses method chaining for compactness and does the same thing:
console.log(Chart_1.getDataSource().getVariables());
Validation
Validation at runtime follows the same logic as for the script editor. Not all validations have to be
performed, for example, validating analytic data like filter values.
Execution Order
On Startup, the R script runs only. The onResultSetChanged event script (JavaScript) doesn’t run
because the widget is in its initial view state.
On data change, the R script runs first, then the onResultChanged event script (JavaScript) runs.
The R environment can be accessed from the JavaScript environment. It can be read from and
manipulated. However, the JavaScript environment can’t be accessed from the R environment.
Reading
Suppose you had an R widget that had a very simple script. It just gets the correlation coefficient
between two measures on a model and puts that into a number named gmCorrelation:
grossMargin <- BestRun_Advanced$`Gross Margin`
grossMarginPlan <- BestRun_Advanced$`Gross Margin Plan`
gmCorrelation <- cor(grossMargin, grossMarginPlan)
Use the getEnvironmentValues on the R widget to access its environment and getNumber to read
a number from the R environment. The following JavaScript code takes the correlation coefficient
from the R environment and logs it to the JavaScript console. Note the this. This code was taken
from the onResultChanged event script of a widget with the above R snippet. It means that R
widgets can be used as global data science scripts:
var nCcor = this.getEnvironmentValues().getNumber("gmCorrelation");
var sCor = nCcor.toString();
console.log("Margin Correlation: " + sCor);
Writing
You can also manipulate the R environment from JavaScript. The magic methods are
getInputParameters and setNumber. The following line of JavaScript sets an R environment
variable named userSelection to 0.
RVisualization_1.getInputParameters().setNumber("userSelection", 0);
Scripting in Analytics Designer 57
Lumira scripts execute on the server. Analytics designer scripts execute in the browser JavaScript
engine. Lumira scripts execute close to the data. Analytics designer scripts execute close to the
user.
Analytics designer isn’t copy-and-paste compatible with Lumira. This is partially a consequence
of the close-to-data vs close-to-user philosophical difference.
Data sources are currently hidden within data-bound widgets and you must access them using
getDataSource(). When standalone data sources become available, you will be able to access
them as global variables, as in Lumira.
Analytics designer isn’t supporting automatic type conversion makes scripts more explicit and
avoids common mistakes. This includes requiring a strict equality comparison operator, whereas
Lumira allowed the use of the double equals comparison operator for expressions of different
types.
Widget Concepts, Script APIs, and Usages 58
Once you have added a widget to the Canvas, you can then use its Builder panel, Styling panel,
and Action Menu to configure its styling and runtime behavior, and even write script to configure
how it interacts with other widgets.
If you need more information about any script API in analytics designer, you can read through the
API Reference document which you can open from the help portal:
https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/latest/en-US/index.html
This is very useful, for example, if you need a specific user interface element, a particular
visualization of data, or a certain functionality in your analytic application that is not provided by
the predefined set of widgets.
https://help.sap.com/viewer/0ac8c6754ff84605a4372468d002f2bf/latest/en-US.
The action menu is a dynamic menu and is only visible if the widget is selected. Different
widgets have different options available, and some of the options are not available in view mode.
5.6 Table
Analytics designer provides a Table script API and a Data Source script API to help analytics
designers use script custom specific logic into their analytic applications.
Besides the common widget script API like getVisible() and setVisible(), the main Table script
API is listed below:
addDimensionToColumns
addDimensionToColumns(dimension: string|Dimension, position?: integer): void
Adds the dimension to the column axis at the specified position. If no position is specified, the
dimension is added as the last dimension of the column axis.
Example:
Table_1.addDimensionToColumns("Location_4nm2e04531");
addDimensionToRows
addDimensionToRows(dimension: string|Dimension, position?: integer): void
Adds the dimension to the row axis at the specified position. If no position is specified, the
dimension is added as the last dimension of the row axis.
Example:
Table_1.addDimensionToRows("Location_4nm2e04531");
getDataSource
getDataSource(): DataSource
Returns the data source of the table. If the table has no data source, undefined is returned. Refer
to the section on the data-related script API.
getDimensionsOnColumns
getDimensionsOnColumns(): Dimension[]
Returns the dimensions on the column axis.
getDimensionsOnRows
getDimensionsOnRows(): Dimension[]
Returns the dimensions on the row axis.
getPlanning
getPlanning(): Planning
Returns the planning object of the table. If the table data source is not of type planning, undefined
is returned.
getSelections
getSelections(): Selection[]
Returns the selections of the chart. You can use the elements of the returned array with the
function DataSource.getData() to get the value of a cell. See also the documentation of Selection
(https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/2019.8/en-
US/doc/Selection.html).
Widget Concepts, Script APIs, and Usages 63
getSelections(): Selection[]
Returns the selections of the chart. You can use the elements of the returned array with the
function DataSource.getData() to get the value of a cell. See also the documentation of Selection
(https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/2019.8/en-
US/doc/Selection.html).
removeDimension
removeDimension(dimension: string|Dimension): void
Removes the dimension from whichever axis it is present on. If the dimension is neither on the
Rows nor Columns axis, the operation is ignored.
Example:
Table_1.removeDimension("Location_4nm2e04531");
openNavigationPanel
openNavigationPanel(navigationPanelOptions?: NavigationPanelOptions): void
Opens the navigation panel of the table. Open the navigation panel in its expanded state by
specifying this in the navigation panel options:
Example:
Table_1.openNavigationPanel({expanded: true});
closeNavigationPanel
closeNavigationPanel(): void
Closes the navigation panel of the table.
Example:
Table_1.closeNavigationPanel();
Note: This script API is available on Table widgets and is applicable to SAP BW models only.
setCompactDisplayEnabled
setCompactDisplayEnabled(axis: TableAxis, enabled: boolean)
Enables or disables the compact display (formerly called Universal Display Hierarchy) on the
specified table rows or columns axis.
isCompactDisplayEnabled
isCompactDisplayEnabled(axis: TableAxis): boolean
Returns whether compact display (formerly called Universal Display Hierarchy) is enabled on the
specified table row or column axis.
Widget Concepts, Script APIs, and Usages 64
Note: This script API is available on Table widgets and is applicable to SAP BW models only.
setZeroSuppression
setZeroSuppression(axis: TableAxis, enabled: boolean)
Enables or disables zero suppression on the specified table rows or column axis.
isZeroSuppressionEnabled
isZeroSuppressionEnabled(axis: TableAxis): boolean
Returns whether zero suppression is enabled on the specified table rows or columns axis.
setActiveDimensionProperties
setActiveDimensionProperties(dimension: string | DimensionInfo, properties:
string[] | DimensionPropertyInfo[])
Sets the active (visible) dimension properties of the specified dimension.
getActiveDimensionProperties
getActiveDimensionProperties(dimension: string | DimensionInfo): string[]
Returns the IDs of the currently active (visible) dimension properties of the specified dimension.
See also section Dimension Properties on the Dimension Properties script API of the Data
Source.
onResultChanged
onResultChanged()
Called when the result set displayed by the table changes.
onSelect()
onSelect()
Called when the user selects within the table.
setScaleFormat(value: NumberFormatScaleFormat)
setDecimalPlaces(value: int, measures?: string[])
setSignDisplay(value: NumberFormatSignDisplay, measures?: string[])
setDisplayUnit(value: NumberFormatDisplayUnit)
}
NumberFormatScaleUnit.Default;
NumberFormatScaleUnit.Unformatted;
NumberFormatScaleUnit.AutoFormatted;
NumberFormatScaleUnit.Thousand;
NumberFormatScaleUnit.Million;
NumberFormatScaleUnit.Billion;
NumberFormatScaleUnit.Default;
NumberFormatScaleFormat.Long;
NumberFormatScaleFormat.Short;
NumberFormatScaleFormat.Default;
NumberFormatDisplayUnit.Default;
NumberFormatDisplayUnit.Row;
NumberFormatDisplayUnit.Column;
NumberFormatDisplayUnit.Cells;
NumberFormatSignDisplay.Default;
NumberFormatSignDisplay.MinusAsParentheses;
NumberFormatSignDisplay.MinusAsPrefix;
NumberFormatSignDisplay.PlusMinusAsPrefix;
5.7 Chart
Besides the common widget script API like getVisible() and setVisible(), the main Chart script
API is listed below:
addDimension
addDimension(dimension: string|Dimension, feed: Feed, position?: integer): void
Adds a dimension to the feed at the specified position. If no position is specified, the dimension
is added at the end of the feed.
Example:
Chart_1.addDimension("Location_4nm2e04531", Feed.CategoryAxis);
addMeasure
addMeasure(measure: string|Measure, feed: Feed, position?: integer): void
Widget Concepts, Script APIs, and Usages 66
Adds the measure to the feed, at the specified position. If no position is specified, the measure is
added at the end of the feed.
Example:
Chart_1.addMeasure("[Account_BestRunJ_sold].[parentId].&[Gross_MarginPlan]",Feed.Va
lueAxis);
getDataSource
getDataSource(): DataSource
Returns the data source of the chart. If the chart has no data source, undefined is returned.
getForecast
getForecast(): Forecast
Returns the forecast of the chart.
getMeasure
getMeasures(feed: Feed): Measure[]
Returns the measures of the feed.
Example:
var measures = Chart_1.getMeasures(Feed.ValueAxis);
getSelections
getSelections(): Selection[]
Returns the selections of the chart. You can use elements of the returned array with the function
DataSource.getData() to get the value of a cell. See also the documentation of Selection
(https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/2019.8/en-
US/doc/Selection.html).
getSmartGrouping
getSmartGrouping(): SmartGrouping
Returns the Smart Grouping of the chart.
removeDimension
removeDimension(dimension: string|Dimension, feed: Feed): void
Removes the dimension from the feed.
Example:
Chart_1.removeDimension("Location_4nm2e04531", Feed.CategoryAxis);
removeMeasure
removeMeasure(measure: string|Measure, feed: Feed): void
Removes the measure from the feed.
Widget Concepts, Script APIs, and Usages 67
Example:
Chart_1.removeMeasure("[Account_BestRunJ_sold].[parentId].&[Gross_MarginPlan]",Feed
.ValueAxis);
onResultChanged
onResultChanged()
Called when the result set displayed by the chart changes.
onSelect
onSelect()
Called when the user selects within the chart.
NumberFormatScaleUnit.Default;
NumberFormatScaleUnit.Unformatted;
NumberFormatScaleUnit.AutoFormatted;
NumberFormatScaleUnit.Thousand;
NumberFormatScaleUnit.Million;
NumberFormatScaleUnit.Billion;
NumberFormatScaleUnit.Default;
NumberFormatScaleFormat.Long;
NumberFormatScaleFormat.Short;
NumberFormatScaleFormat.Default;
NumberFormatSignDisplay.Default;
NumberFormatSignDisplay.MinusAsParentheses;
NumberFormatSignDisplay.MinusAsPrefix;
NumberFormatSignDisplay.PlusMinusAsPrefix;
Widget Concepts, Script APIs, and Usages 68
Before this script API has had been introduced, you could retrieve individual data cells using
DataSource.getData(). However, it was not possible to retrieve all members for a dimension in a
specific result set.
Note: To reference in a selection the NULL member, use the alias Alias.NullMember.
Note: To reference in a selection the totals member, use the alias Alias.TotalsMember.
To help you understand using this script API method, we list several examples. As ID of dimension
and measure is used in input parameter and returned by the result set, we choose to display both
ID and description for tables and charts in these examples.
Function Summary:
// Returns the result set according to the selected data or context of
// the data you select. Offset/limit should be not less than zero. If
// offset/limit are invalid or not set, all data is returned.
// If the selection does not specify any MeasureDimension, all measures
// are returned.
Chart_1.getDataSource().getResultSet(selection?: Selection | Selection[] |
SelectionContext, offset?: integer, limit?: integer): ResultSet[]
Table_1.getDataSource().getResultSet(selection?: Selection | Selection[] |
SelectionContext, offset?: integer, limit?: integer): ResultSet[]
ResultSet {
[key: string]: DataContext;
}
Selection {
[key: string]: string;
}
SelectionContext {
Widget Concepts, Script APIs, and Usages 69
DataContext {
id: string
description: string
formattedValue: string
rawValue: string
parentId: string
properties?: { // "properties" doesn't contain "id" or "description"
[key: string]: [string]
}
}
Here is an example that shows the result when no input parameter is specified: All data points of
Chart_1 are returned. Dimension context includes parent information if it has a hierarchy structure.
Measure context includes formattedValue and rawValue.
The below chart shows Gross Margin per Location. Include Parent Levels has been checked in
the Builder panel.
Chart_1.getDataSource().getResultSet();
// ResultSet[].
// Both CT1 and CT2 have parentId "SA1". The result set is partially listed.
[{
"@MeasureDimension": {
Widget Concepts, Script APIs, and Usages 70
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"formattedValue": "173.48",
"rawValue": "173481592.97"
},
"Location_4nm2e04531": {
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"description": "California"
}
}, {
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"formattedValue": "48.97",
"rawValue": "48971999.74"
},
"Location_4nm2e04531": {
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]",
"description": "Los Angeles",
"parentId": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]" // CT1's
parentId is "SA1"
}
}, {
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"formattedValue": "19.62",
"rawValue": "19619690.4"
},
"Location_4nm2e04531": {
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT2]",
"description": "San Francisco",
"parentId": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]" // CT2's
parentId is "SA1"
}
}, {
...
}]
Example 2: Get a result set when a data point in a chart has more than one measure
If a chart has more than one measure, such as a bubble or scatter chart, each measure combined
with its dimension context represents a cell in the ResultSet array. For example, one data point
of a bubble chart has three cells in the ResultSet array. They represent Gross Margin Per
Location, Profit Per Location, and Original Sales Price Per Location, respectively.
Chart_1.getDataSource().getResultSet();
// ResultSet[].
// Three cells of ResultSet array represent one data point of a bubble chart.
[{
Widget Concepts, Script APIs, and Usages 71
At both design time and runtime, additional columns can be added to the table by right-clicking a
table cell and adding a calculation. As these generated columns are part of the table, they are
also returned in the result set.
Widget Concepts, Script APIs, and Usages 72
Example 3: Newly added row has the description “Sum” and its ID is a UUID
Table_1.getDataSource().getResultSet();
}]
Table_1.getDataSource().getResultSet();
In the below example, a comment column is added to a table and one comment cell is input with
text.
Widget Concepts, Script APIs, and Usages 74
Table_1.getDataSource().getResultSet();
// ResultSet[]
[{
"Version": {
"id": "public.Actual",
"description": " Actual"
},
"@MeasureDimension": {
"id": "[Account].[parentId].&[TOTAL]",
"description": "TOTAL",
"rawValue": "61254901.96",
"formattedValue": "61,254,901.96"
}
}, {
"Version": {
"id": "24769565-5194-4582-9346-349222998310",
"description": " Comment"
},
"@MeasureDimension": {
"id": "[Account].[parentId].&[TOTAL]",
"description": "TOTAL",
"rawValue": "comment test",
"formattedValue": "comment test"
}
}, {
Widget Concepts, Script APIs, and Usages 75
...
}]
The table row can be hidden, removed, or excluded. In these cases, only visible rows are returned
by the getResultSet() script API method.
If there are many rows in one table and a scroll bar is displayed, even though some of the rows
are temporarily invisible, the getResultSet() script API method still returns all data cells in this
table because all data is already fetched and sent to the frontend.
Users can specify a concrete selection of a data cell to get its description, parentId,
formattedValue, and rawValue.
Example 7: Specify Selection parameter, location dimension member equals CT1 or CT2
The Selection parameter can be an array and multiple filters are supported. The below condition
describes that the location dimension member should equal CT1 or CT2.
// Input parameter is Selection[]
Chart_1.getDataSource().getResultSet([{
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]"
}, {
Widget Concepts, Script APIs, and Usages 76
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT2]"
}]);
We also introduce the SelectionContext as a parameter. As measure is the same (Gross Margin)
in this case, the measure ID is specified only once. The below example has the same result set
as the previous one.
Chart_1.getDataSource().getResultSet({
"@MeasureDimension": ["[Account_BestRunJ_sold].[parentId].&[Gross_Margin]"],
"Location_4nm2e04531":
["[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]",
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT2]"]
});
Example 9: Selection specified by input parameter is not targeted to a certain data cell
If the input parameter is not a concrete data cell selection, all data cells matching this condition
are returned.
Widget Concepts, Script APIs, and Usages 77
Table_1.getDataSource().getResultSet({
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]"
});
// ResultSet[].
// Five data cells are returned as one data cell is selected by two selection.
[{
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"rawValue": "25388481.78",
"formattedValue" "25.39"
},
"Location_4nm2e04531": {
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"description": "California"
},
"Product_3e315003an": {
"id": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]",
"description": "Alcohol"
}
}, {
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Discount]",
"description": "Discount",
"rawValue": "3765388.14",
"formattedValue" "3.77"
},
"Location_4nm2e04531": {
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA2]",
"description": "Nevada"
},
"Product_3e315003an": {
"id": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC1]",
Widget Concepts, Script APIs, and Usages 79
Example 11: How to define and use parameter offset and limit
Parameter offset and limit are used to limit the amount of data cells.
Offset skips the offset cells before beginning to return the cells.
The data point context of the actual date has the measure actualValue. If there is fitted line in this
date, another measure hindcastValue can be found.
The data point context of the forecast date has three measures: upperBound, lowerBound, and
forecastValue.
Example 12: Time Series chart with forecast enabled, extra data point is generated
The Time Series chart below has both an actual date and a forecast date. The blue area is
generated by the forecast.
Chart_1.getDataSource().getResultSet();
// ResultSet[]
[{
"Date_703i1904sd.CALMONTH": {
"id": "201409",
"description": "Sep 2014" // actual date
},
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"rawValue": "4207974.43",
"formattedValue": "4207974.43"
Widget Concepts, Script APIs, and Usages 81
},
"@Forecast": {
"id": "actualValue"
}
}, {
"Date_703i1904sd.CALMONTH": {
"id": "201409",
"description": "Sep 2014" // actual date
},
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"rawValue": "5903071.626874865",
"formattedValue": "5903071.626874865"
},
"@Forecast": {
"id": "hindcastValue" // fitted line in dashed line
}
},
...
{
"Date_703i1904sd.CALMONTH": {
"id": "1498867200000",
"description": "Jul 2017" // forecast date
},
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"rawValue": "7115040.662185087",
"formattedValue": "7115040.662185087"
},
"@Forecast": {
"id": "forecastValue"
}
}, {
"Date_703i1904sd.CALMONTH": {
"id": "1498867200000",
"description": "Jul 2017" // forecast date
},
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"rawValue": "10076952.088587102",
"formattedValue": "10076952.088587102"
},
"@Forecast": {
"id": "upperBound"
}
}, {
Widget Concepts, Script APIs, and Usages 82
"Date_703i1904sd.CALMONTH": {
"id": "1498867200000",
"description": "Jul 2017" // forecast date
},
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"description": "Gross Margin",
"rawValue": "4153129.2357830727",
"formattedValue": "4153129.2357830727"
},
"@Forecast": {
"id": "lowerBound"
}
}]
Example 13: Bubble chart with smart grouping enabled, extra measure is generated
If smart grouping is enabled in a bubble chart, it generates one extra measure to identify the group
of each data point.
Chart_1.getDataSource().getResultSet();
// ResultSet[]
[{
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Discount]",
"description": "Discount",
"formattedValue": "1184733.37",
Widget Concepts, Script APIs, and Usages 83
"rawValue": "1184733.37"
},
"Store_3z2g5g06m4": {
"id": "ST1",
"description": "Second Hand"
},
"Smart Group": {
"id": "Predictive Clustering Group 1", // this data cell is in group 1
"description": "Group1"
}
},
...
{
"@MeasureDimension": {
"id": "[Account_BestRunJ_sold].[parentId].&[Discount]",
"description": "Discount",
"formattedValue": "1969249.25",
"rawValue": "1969249.25"
},
"Store_3z2g5g06m4": {
"id": "ST38",
"description": "Henrys Corner Store"
},
"Smart Group": {
"id": "Predictive Clustering Group 2", // this data cell is in group 2
"description": "Group 2"
}
}, {
...
}]
"description": "AUS/NRD",
"parentId": undefined,
"properties": {
"0BC_REG.MIDDLE_TEXT": "AUS/NRD",
"0BC_REG.DISPLAY_KEY_NC": "NRD"
}
}
},
...,
{
"@MeasureDimension": {
"id": "00O2TLJDAAI84OCJLDKA0280K",
"description": "0BC_AMT",
"parentId": undefined,
"rawValue": "3649995.68"
"formattedValue": "DM3,649,995.68"
},
"0BC_REG": { // no properties are returned here as the
// total value doesn't have properties
"id": "SUMME",
"description": "Totals",
"parentId": undefined
}
}]
The method getResultSet() returns an array of ResultSet objects. Each ResultSet is an object
of DataContext objects. A DataContext object can contain the property properties that contains
the visible properties. Or more formally:
Table_1.getDataSource().getResultSet(selection?: Selection | Selection[] |
SelectionContext, offset?: integer, limit?: integer): ResultSet[]
Function Summary
// Returns the selection of data cells
// Offset/limit should be not less than zero.
// If offset/limit is invalid or not set, all data is returned.
// If the selection doesn't specify any MeasureDimension, all measures
// are returned.
Chart_1.getDataSource().getDataSelections(selection?: Selection | Selection[] |
SelectionContext, offset?: integer, limit?: integer): Selection[]
Table_1.getDataSource().getDataSelections(selection?: Selection | Selection[] |
SelectionContext, offset?: integer, limit?: integer): Selection[]
Let’s use the previous chart from example 1 with Gross Margin per Location.
Widget Concepts, Script APIs, and Usages 86
// Selection[]
[{
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]"
}, {
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC1]"
}, {
...
}]
Let’s use the previous example 9 Gross Margin per Location and Product.
// Selection[]
[{
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]"
}, {
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]",
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC1]"
}, {
...
}]
Widget Concepts, Script APIs, and Usages 87
For a dimension which has attributes, the attributes will be provided as additional properties of
the ResultMemberInfo object.
Function Summary
// Get member metadata, works for data cell and header cell
Chart_1.getDataSource().getResultMember(dimension: String | DimensionInfo,
selection: Selection): ResultMemberInfo | undefined
Table_1.getDataSource().getResultMember(dimension: String | DimensionInfo,
selection: Selection): ResultMemberInfo | undefined
ResultMemberInfo {
id: string
description: string
parentId: string
properties?: { // "properties" doesn't contain "id" or "description"
[key: string]: [string]
}
}
Table_1.getDataSource().getResultMember("Location_4nm2e04531", {
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]",
"Product_3e315003an":
"[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Gross_Margin]"
Widget Concepts, Script APIs, and Usages 88
});
// ResultMember
{
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]",
"description": "Los Angeles",
"parentId": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]"
}
The below example identifies a header member instead of a single data cell.
Table_1.getDataSource().getResultMember("Location_4nm2e04531", {
"Location_4nm2e04531":
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]"
});
// ResultMember
{
"id": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"description": "California"
}
Example 19: Return undefined if more than one result set member found
If the input parameter can't identify a single result member, for example, both California and Los
Angeles have an Alcohol product, this script API method returns undefined.
Table_1.getDataSource().getResultMember("Location_4nm2e04531", {
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]"
});
// ResultMember
undefined
If the same logic is applied to the table below, California is returned, as there is only one location
in that table.
Widget Concepts, Script APIs, and Usages 89
Figure 46 Return "undefined" If More Than One Result Set Member Found
The method getResultMember() returns an ResultMemberInfo object. It can contain the property
properties that contains the visible properties. Or more formally:
Table_1.getDataSource().getResultMember(dimension: String | DimensionInfo,
selection: Selection) : ResultMemberInfo
To do this, you can write a script with the following script API:
Application.refreshData(datasources: [DataSource]);
After calling Application.refreshData(), the next line of script will wait until it finishes to run, to
ensure the application gets the latest data.
To do this, you can write a script with the following script API:
Widget.getDataSource().refreshData();
The widget should be a chart or table. Currently R visualization, Geo, and custom widgets are not
supported.
Note: Even if there are some widgets, for example charts created based on the same data source,
refreshing one chart won’t cause the other charts to refresh automatically.
Write the onInitialization event script of the Canvas to refresh Chart_1 and Table_1 when
initializing an application:
var ds1 = Chart_1.getDataSource();
var ds2 = Table_1.getDataSource();
Application.refreshData([ds1, ds2]);
Use the Refresh Data script API together with the Timer script API to refresh a widget periodically:
// Write the script for the onTimeout event of Timer_1
// to refresh data of Chart_1 and Chart_2 every one minute.
Chart_1.getDataSource().refreshData();
Chart_2.getDataSource().refreshData();
Timer_1.start(60);
Example:
In the following example, the Prompt dialog of a table’s data source is opened:
Table_1.getDataSource().openPromptDialog();
Widget Concepts, Script APIs, and Usages 92
Example:
In the following example, the names of all variables of a data source are printed to the browser
console:
var aVariables = Table_1.getDataSource().getVariables();
for (var i = 0; i < aVariables.length; i++) {
console.log(aVariables[i].id);
}
Note: By default, this function will apply variable values of a variable to the model used by the
data source of the application. The widget can be configured such that variables are applied to
the model of the widget only (see Figure 49). You can find out, for example, in the title area of the
table whether the variables are applied on the model of the data source of the application (grey
braces) or on the model of the widget only (blue braces) (see Figure 50).
Figure 49: Prompt Dialog: Variable Values Are Applied to the Widget Only
Widget Concepts, Script APIs, and Usages 93
Figure 50: Variable Values Are Applied to the Model of the Application or the Widget
Note: This method is not validating the specified variable values neither at runtime nor at design
time. All values and value combinations which are accepted in the Prompt dialog will be
supported. All other combinations might lead to errors or inconsistent state.
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {value: "5"});
or, alternatively,
Table_1.getDataSource().setVariableValue("VAR_NAME", "5");
If the variable supports excluding a single variable value, you can set the variable value as follows:
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {exclude: true, value: "5"});
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {values: ["5", "7"]});
If the variable supports excluding multiple values, you can set the variable value as follows:
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {exclude: true, values: ["5",
"7"]});
5.10.3.3 Comparisons
If the variable supports comparison operations <, <=, >, and >= you can set the variable value as
follows:
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {less: "5"});
Table_1.getDataSource().setVariableValue("VAR_NAME", {lessOrEqual: "5"});
Table_1.getDataSource().setVariableValue("VAR_NAME", {greater: "5"});
Table_1.getDataSource().setVariableValue("VAR_NAME", {greaterOrEqual: "5"});
Widget Concepts, Script APIs, and Usages 94
5.10.3.4 Ranges
If the variable supports a range of variable values, you can set the variable value as follows:
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {from: "5", to: "7"});
If the variable supports excluding a range of variable values, you can set the variable value as
follows:
Example:
Table_1.getDataSource().setVariableValue("VAR_NAME", {exclude: true, from: "5", to:
"7"});
Example:
In the following example, the variable values of the variable V_Country are retrieved:
var values = Table_1.getDataSource().getVariableValues("V_Country");
Each value in the array is an instance of either SingleVariableValue, MultipleVariableValue, or
RangeVariableValue, which all inherit from VariableValue. To work with such an instance, that is,
to access its type-specific properties, cast the instance to the corresponding type first, using the
type property. The following example shows how:
var value = Table_1.getDataSource().getVariableValues("V_Country")[0];
switch (value.type) {
case VariableValueType.Single:
var singleValue = cast(Type.SingleVariableValue, value);
console.log(singleValue.value); // you can access the 'value' property now
break;
case VariableValueType.Multiple:
var multiValue = cast(Type.MultipleVariableValue, value);
console.log(multiValue.values); // you can access the 'values' property now
break;
case VariableValueType.Range:
var rangeValue = cast(Type.RangeVariableValue, value);
console.log(rangeValue.from); // you can access the 'from' property now
console.log(rangeValue.to); // you can access the 'to' property now
// further range properties: 'less', 'lessOrEqual', 'greater', 'greaterOrEqual'
break;
default:
break;
}
Note: What you set with setVariableValue() is not necessarily the same that is returned from
getVariableValues().
Widget Concepts, Script APIs, and Usages 95
Example:
If you have several options to set multiple values, then the following lines are equivalent:
Table_1.getDataSource().setVariableValue("V_Country", {values: ["DE", "FR"],
exclude: true});
Table_1.getDataSource().setVariableValue("V_Country", [{value: "DE", exclude:
true}, {value: "FR", exclude: true}]);
getVariableValues() always returns as few VariableValue objects as possible, grouping all
single values with the same sign, that is including or excluding, into one values object. In the
example, an array with only one object is returned:
[{values: ["DE", "FR"], exclude: true}]
In SAP BW models you can define variables that correspond to user-modifiable settings like filters
or hierarchies. For example, setting a variable value sets the exact same value as the filter on the
related dimension.
Note: Synchronization in the other direction is not performed by getVariableValues(). Thus, the
getVariableValues() still returns the same variable value, even though the user might have
modified the filter in the meantime. To retrieve the current value of such special variables, use
getDimensionFilters() instead. For hierarchy variables use getHierarchy().
Note: If you remove a variable value from a mandatory variable, then this operation is ignored.
Example:
Note: If you copy an empty variable value to a mandatory variable then copying this variable value
is ignored.
Note: If you copy a variable value to a data source of a widget that overrides variables and the
variable is of type text, then copying this variable value is ignored.
Example:
In the following example, the variable value of variable V_Country is copied from data source 1 to
data source 2:
var DS_1 = Table_1.getDataSource();
Table_2.getDataSource().copyVariableValueFrom(DS_1, "V_Country");
Widget Concepts, Script APIs, and Usages 96
Example:
In the following example, the variable values of variables V_Country and V_Supervisor are copied
from data source 1 to data source 2:
var DS_1 = Table_1.getDataSource();
Table_2.getDataSource().copyVariableValueFrom(DS_1, ["V_Country", "V_Supervisor"]);
Example:
In the following example, the variable values of all variables are copied from data source 1 to data
source 2:
var DS_1 = Table_1.getDataSource();
Table_2.getDataSource().copyVariableValueFrom(DS_1);
For instance, a Popup can show a description of the application, and another Popup can ask the
user to perform configurations. Because the popup acts as a container widget, you can put any
other widget into the popup, such as a table, button, or checkbox.
You can choose to design a popup starting from scratch. Start with an empty Canvas and have
the flexibility to add whatever widget you want. You can enable the header and footer setting to
turn the popup directly into a popup dialog that has a consistent look and feel compared to other
dialogs in SAP Analytics Cloud stories.
close
close(): void
getTitle
open
setTitle
isButtonEnabled
isButtonEnabled(buttonId: string): boolean
Returns whether the specified button in the footer of the popup is enabled.
isButtonVisible
isButtonVisible(buttonId: string): boolean
Returns whether the specified button in the footer of the popup is visible.
setButtonEnabled
setButtonEnabled(buttonId: string, enabled: boolean): void
Enables or disables the specified button in the footer of the popup.
setButtonVisible
setButtonVisible(buttonId: string, visible: boolean): void
Shows or hides the specified button in the footer of the popup.
onButtonClick
onButtonClick(buttonId: string)
Called when the user clicks one of the buttons in the footer of the popup.
onButtonClick
onButtonClick(buttonId: string)
Called when the user clicks one of the buttons in the footer of the popup.
Need to add at least two widgets to a popup to run the popup as designed
We recommend you add at least two widgets to a popup as widgets are the visualization of the
popup. If no widgets are added, you won't see the popup displayed when you trigger it while
running the analytic application. If only one widget is added, the height and width you set for the
popup won't take effect.
When a table or chart in the Canvas act as the source widget of a filter line widget in a
popup, source widget can’t find the filter line as its reference after reloading the analytic
application
In the case when a table or chart in the Canvas act as the source widget of a filter line widget in
a popup and you reopen or refresh the analytic application, you will find the filter line isn’t listed
in the reference list of the table or chart widget after you choose Find Reference. This is because
currently we don't initiate the filter line widget in the popup when you first entering an analytic
application.
Widget Concepts, Script APIs, and Usages 98
To solve this, for now we recommend you activate the popups by clicking on each of them. Then
the reference list will display all relevant results.
The text style can be configured by each segment. In-place edit the text by double-clicking the
Text input field of Text_Title in Canvas and config the style of description.
The script variable can be exposed as URL parameter if you switch on the option. For example,
if you input p_ScriptVariable_Currency=CNY in the URL link, you’ll get the following:
Configure Feeds
The RSS feeds in the widget can be updated dynamically at runtime via a script API when you
select in Chart_RSSCategory in Present relevant RSS articles.
Widget Concepts, Script APIs, and Usages 99
Example:
If you select Business in the chart, BBC Business is added in the list of feeds and selected by
default after running the scripts below:
RssReader_Content.removeAllFeeds();
RssReader_Content.addFeed("BBCBusiness","http://feeds.bbci.co.uk/news/business/rss.
xml");
RssReader_Content.setSelectedFeed("http://feeds.bbci.co.uk/news/business/rss.xml")
5.14 R Visualization
Use the R Visualization widget to leverage R scripts. It allows you to build your own visualizations,
do calculation, and more. Refer to sample Show R Visualization result in Text for the most
frequently used script API methods.
In the script of R Visualization, you can define parameters to get input values or return results
calculated in the script.
Example:
Configure the title of visualization R Visualization in Show R Visualization result in Text per
location by input parameter:
RVisualization.getInputParameters().setString("titleParam", "Gross Margin of
Oregon");
Example:
Calculate the total of gross margin in RVisualization script, and return the result:
RVisualization.getEnvironmentValues().getNumber("totalSum");
Configure the data source of the R Visualization via script API. For example, in Show R
Visualization result in Text, the dimension filter is set to Oregon when you change location via
Dropdown_Location by this
RVisualization.getDataFrame("BestRunJuice_SampleModel").getDataSource().setDimensio
nFilter("Location_4nm2e04531", ["CT13", "CT14", "CT15", "CT16", "CT17", "CT18"]);
Widget Concepts, Script APIs, and Usages 100
The Geo Map widget in an analytic application has the same capabilities as in a story, and also
provides a script API to make changes by scripting.
Since a Geo Map widget can have multiple visualization layers on the top, there is a script API to
control their visibility so users can decide which layers they need to see.
GeoMap_1.getLayer(0).setVisible(true);
GeoMap_1.getLayer(0).isVisible();
5.16 Timer
The Timer widget enables you to start a timer to trigger timing events. By leveraging the feature
of a timer, you can realize different scenarios such as:
• Create animations
• Send notifications to end users regularly
• Refresh your analytic application in a certain interval of time
To further delve into its usage, I will share two samples for your reference.
In this sample, we add animation to the header above, making the tiles (widgets) shift from right
to left repeatedly.
To make a TabStrip widget look like a page book, a small tip is to hide the header of the Tabstrip,
for example, using a shape, then use the script API method TabStrip_1.setSelectedKey(TabID)
to dynamically “slide” the tab.
o Name
• Size and Position
• Widget
o Background Color
o Border
• Actions
o Show at view time or not
o Order
• Listbox Style
o Selected
o Mouse Hover
o Mouse Down
• Font
5.17.4 Events
The onSelect event is triggered when the end user makes a selection at runtime.
getLayout(): Layout
// Selects an item in the List Box. The item is specified by its key.
setSelectedKey(key: string): void
// Selects items in the List Box. The items are specified by their keys.
// If the List Box is single selection, the first item of the specified keys
// will be selected.
setSelectedKeys(key: string[]): void
// Removes an item from the List Box. The item is specified by its key.
removeItem(key: string): void
We don’t have the mechanism yet to automatically flow the widgets when the screen size changes,
which will be introduced in future. But we can cover some of the responsive scenarios by
combining dynamic layout and script APIs. In an analytic application, more than just flow UI, you
have the flexibility to add a widget on top of a background shape, overlapping but not flow them,
and they can shrink or grow in the same proportion when the window size changes.
You can set each widget's Left, Width, Right and Top, Height, Bottom values in Pixel, Percentage
and Auto (relative to its parent container, root Canvas if not in a container) values on the Styling
panel's Layout Section.
In order to adapt to the screen real-estate at runtime on different machines or browser window,
you need to set the unit to percentage (%) or auto.
The application’s onResize event is dispatched every 500 ms when the application window resizes.
Inside the onResize event script, you can use the Layout script API to dynamically set the size
and position.
Widget Concepts, Script APIs, and Usages 108
Below code sample shows how to adjust the layout to fit a small screen size like phone.
// small screen size
if (screenWidth < 500 || screenHeight < 500) {
Panel_3.setVisible(false);
Panel_2.getLayout().setWidth(LayoutValue.create(98,
LayoutUnit.Percent));
Panel_2.getLayout().setBottom(LayoutValue.Auto);
Panel_2.getLayout().setHeight(LayoutValue.create(376,
LayoutUnit.Pixel));
Panel_3.getLayout().setBottom(LayoutValue.Auto);
Panel_3.getLayout().setLeft(LayoutValue.create(1,
LayoutUnit.Percent));
Panel_3.getLayout().setTop(LayoutValue.create(476,
LayoutUnit.Pixel));
if (screenWidth < 500) {
//one column
Panel_3.getLayout().setHeight(LayoutValue.create(
(baseChartHeight + padding) * 4 + padding * 3 +
Table_1.getLayout().getHeight().value, LayoutUnit.Pixel));
} else {
//two columns
Panel_3.getLayout().setHeight(LayoutValue.create(840,
LayoutUnit.Pixel));
}
Panel_3.setVisible(true);
}
With the Layout script API, you have all the flexibility to adjust the application based on the screen
size, to create a responsive application in an analytic application.
To specify a theme, use the syntax Application.setTheme(), type CTRL+Space and the theme
selector will automatically pop up for you to choose the theme you want to apply from
the Files repository. After you choose a theme, the corresponding theme ID will be displayed in
the syntax. If you choose not to define a theme in the syntax, then the application will apply the
default light theme instead.
Note: Currently, there's a limitation that calling the setTheme() script API method in a popup
doesn't affect the theme settings in the popup. To solve this, you can add a panel to a popup and
include all widgets in it. Then, when you trigger the setTheme() script API method in a popup, all
widgets added to the panel will apply the new settings.
Here's an example that shows how to leverage the Set Theme script API to allow end users to
switch between different themes for your application:
Widget Concepts, Script APIs, and Usages 109
First add a dropdown widget Theme_Dropdown to the Canvas. In the Builder panel of the dropdown,
fill the value column with the theme IDs and the text column with corresponding theme names.
In addition to use the Set Theme script API, you can also enable end users to apply a specific
theme when loading an analytic application by directly adding the theme ID to the application's
URL address. For example, like this:
https://master-app-building
/sap/fpa/ui/bo/application/4FA12EC04829FDC682399273A7A3A0C?mode=embed;themeId=D991AAE
EC518947626D749EDFF57D64C
In the Styling panel, you can enable or disable the automatic loading indicator.
Besides the configuration in the Styling panel at design time, the automatic loading indicator can
be also turned on and off via scripting:
// Enable/disable automatic loading indicator
Application.setAutomaticBusyIndicatorEnabled(true);
The script API is available for the following objects: Application, Popup, and container widgets,
such as TabStrip and Panel. The text shown along with the indicator can be configured via an
optional parameter.
// Show loading indicator, add text to loading indicator if text is specified
Application.showBusyIndicator("Loading the application"); // cover the whole
application page
Popup_1.showBusyIndicator("Loading the popup"); // cover the popup
TabStrip_1.showBusyIndicator("Loading the tab strip"); // cover the tab strip
Panel_1.showBusyIndicator("Loading the panel"); // cover the panel
When applying a bookmark, the operation is applied to the bookmark's Included Components
configured at design time.
// Apply the bookmark to the current analytic application. Returns false if the
bookmark is not accessible to the current user. The input is the bookmark ID or a
BookmarkInfo object.
BookmarkSet_1.apply(bookmark);
Widget Concepts, Script APIs, and Usages 111
The saveBookmark() method of the BookmarkSet component. It has the following signature:
BookmarkSet.saveBookmark(BookmarkSaveInfo: BookmarkSaveInfo, overwrite?: boolean):
BookmarkInfo
Note: This method replaces the method save(), which is deprecated.
The BookmarkSaveInfo class, which is passed to saveBookmark() and contains properties as key-
value pairs:
class BookmarkSaveInfo {
name: string,
isGlobal?: boolean,
isDefault?: boolean,
properties?: { [key: string]: string | undefined }
}
The Bookmark class, which is returned by saveBookmark() and contains properties:
class BookmarkInfo {
id: string,
name: string,
displayName: string,
version: integer,
isGlobal: boolean,
isDefault?: boolean,
properties?: { [key: string]: string | undefined }
}
The option isDefault indicates whether the bookmark is the default bookmark. The default
bookmark is loaded when the analytic application’s URL contains the URL parameter
bookmarkId=DEFAULT.
Example:
In the following example, the model ID is saved along with the bookmark. This additional
information is used to list all bookmarks which use the model ID "modelId":
// Create bookmark properties (key-value pairs)
var oBookmarkProperty1 = { "modelId": "BestRunJuice" };
var oBookmarkProperty2 = { "modelId": "BusinessPlanning" };
if (aBookmarkInfo[i].properties) {
for (var j = 0; j < aBookmarkInfo[i].properties.length; j++) {
if (aBookmarkInfo[i].properties[j] &&
aBookmarkInfo[i].properties[j]["modelId"] === "BestRunJuice") {
// print the name of bookmark which uses the "BestRunJuice" model
console.log(aBookmarkInfo[i].name);
}
}
}
}
Notification Panel
Email template
Embed,
View
}
NotificationOptions {
title: string,
content?: string,
receivers?: string[], // default: current application user
mode?: ApplicationMode, // default: Present
parameters?: UrlParameter[], // array, no optional single URL parameter
isSendEmail?: boolean, // default: false
IsSendMobileNotification?: boolean // default: false
}
Examples:
// move widget to container
Popup
There is no script API to move a widget to a popup, but you can add a container (panel or tab
strip) to the popup and move the widget to this container.
You can’t move a widget, whose parent is a popup, to other containers. For example, consider
this containment hierarchy: Popup_1 – Panel_1 – Button_1. You can‘t move Panel_1 to another
container on the Canvas, but you can move Button_1.
Bookmark
You can bookmark the state after the moveWidget() script API call.
Example:
Run the application. Move Button_1 to Panel_1 via the moveWidget() script API method. Save the
bookmark.
Open the application again, move Button_1 to Panel_2 and delete Panel_1. Save the application.
In this case, it is recommended that the application developer should update the bookmark
version.
• Input Field
• List Box
• Radio Button Group
• Range Slider
• Slider
• Text Area
A value that an end user selected or updated at runtime can be written back to a specific Script
Variable as well.
InputField, TextArea
• Bindable values: Text
• Supported bindings: Script Variable, Tile Filter & Variable, Model Variable, Application
Property (incl. Current User/Time/Date, Last Modified Date, Last Modified Date/Time,
Last Modifier, Creator)
Widget Concepts, Script APIs, and Usages 117
Slider, RangeSlider
• Bindable values: (Slider) Current value; (Range Slider) Start value, End value
• Supported Bindings: Script Variable, Tile Filter & Variable, Model Variable
Image
• Bindable values: Image URL
• Supported Bindings: Script Variable
Widget Concepts, Script APIs, and Usages 118
Example:
In the following example, a customer has three SAP Analytic Cloud (SAC) tenants and three 3
SAP BW landscapes (Dev, Acceptance, and Production). The customer wants the SAC analytic
applications to check from which SAC tenant an analytic application has been launched wants to
open the WebInteligence report on the corresponding SAP BW landscape with the help of the
Hyperlink functionality of the Navigation script API:
// Example:
Widget Concepts, Script APIs, and Usages 119
You can also use the script API to suppress system messages (success, warning, info) in some
cases in order not to annoy end users. Fatal errors and reload cannot be suppressed because it
is critical to let end users know. Messages will be written to the console even if they are
suppressed by setMessageTypesToShow() for supportability reasons.
Script API
enum ApplicationMessageType {
Success,
Info,
Warning,
Error
}
New events
Application:
// triggered before Application.onResize() event
onOrientationChange(angle: DeviceOrientation)
onShake()
Button, Image, Shape:
onLongPress()
Widget Concepts, Script APIs, and Usages 120
For example, you can set the background color based on a value.
Without this script API, a trick to achieve this was to create two text widgets, each with a red or
green background color. Then setVisible() was used to control which text widget should be
shown. This increased the number of widgets.
Script API
Text.setStyle({
backgroundColor: value,
color: value
}): void
InputField.setStyle({
backgroundColor: value,
borderColor: value,
color: value
}): void
TextArea.setStyle({
backgroundColor: value,
borderColor: value
}): void
Shape.setStyle({
fillColor: value,
lineColor: value
}): void
Add a Value Driver Tree widget from the “+” icon in the toolbar.
Users can configure the styling options of a Value Driver Tree widget via its Styling panel.
Runtime Interactions
• Search Node
• Change Data
Widget Concepts, Script APIs, and Usages 124
Script API
The Value Driver Tree widget provides, like other widgets, a script API such as:
// Returns the layout of the widget
getLayout(): Layout
To enable this capability, you, as an analytic application developer, select Pause Data Refresh in
the Builder panel of the Table or Chart widget at design time.
Widget Concepts, Script APIs, and Usages 125
Besides configuring the Table or Chart Builder panel at design time, you, as an analytic
application developer, can use the following script 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 script 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. Thus, it’s useful to
enable or disable interactions on a widget. You, as an analytic application developer, can use the
following script 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
Widget Concepts, Script APIs, and Usages 126
enum DataExplorerSortOrder {
Argument, // Align with the order specified in setAdditionalDimensions()
Default // Sort in default order (ascending alphabetical order)
}
To apply Explorer results to a chart, add custom menu items to the Explorer's visualization dialog
as follows:
1. In Outline > Scripting > Data Explorer Configuration, add a new configuration.
2. A Data Explorer Configuration panel opens at the right side of the Canvas.
3. In this panel, add custom menu items in the Menu Settings.
Widget Concepts, Script APIs, and Usages 127
4. Add an onMenuItemSelect event script to the new configuration by clicking the fx button to
the right of the new configuration in the Outline, then implement in the script the actions
you want to trigger when these menu items are selected. The following example applies
your changes to Chart_1 in the Explorer dialog to Chart_2:
var explorer = Chart_1.getDataSource().getDataExplorer();
if (Chart_2.isVisible() === false) {
DataExplorerConfiguration_1.applyToWidget(Chart_2);
Chart_2.setVisible(true);
}
5. Save and run the analytic application.
6. Find your custom menu items in the Explorer.
Note: When you apply Explorer results to a chart or table, then the results of a chart can only be
applied to a chart, and results of a table can only be applied to a table. The automatic transfer of
the Show/Hide configuration in the Explorer, as well as the original chart or table styling to the
target chart or table is not supported.
For instance, in the example Layout 2, you want to have a sidebar always on the left and content,
which will flow based on the size of the screen, always on the right. You can achieve this layout
by using a normal Panel widget for the content on the left with its width set to 20% of the screen
width, and a Flow Panel widget for the content on the right with its width set to 80% of the screen
width.
Widget Concepts, Script APIs, and Usages 128
Besides using a Flow Panel widget and other widgets side by side, you can even use a normal
Panel widget nested inside a Flow Panel widget to create more sophisticated layouts.
Note: Not all CSS class names and properties are supported. With future releases more and more
CSS class names and properties will be supported.
Saving this analytic application also saves this application CSS within the analytic application.
For instance, the Global Default Class Name is set to my-theme, which includes the CSS settings
for all Chart and Table widgets, so that you don’t need to specify a CSS class for each specific
Chart or Table widget.
Widget Concepts, Script APIs, and Usages 132
Widget Concepts, Script APIs, and Usages 133
You, as an analytic application developer, can use the following script API of the Calendar
Integration component to retrieve the current calendar task, a CalendarTask object:
CalendarIntegration.getCurrentTask(): CalendarTask
You can use the following script API of the CalendarTask object to retrieve its status and type (for
example, General Task, Composite Task, and Review Task) and you can also check if the
currently logged-in user has a specific calendar task user role type in relation to this calendar
task:
• CalendarTask.getStatus(): CalendarTaskStatus
• CalendarTask.getType(): CalendarTaskType
• CalendarTask.hasUserRole(CalendarTaskUserRoleType): boolean
Depending on the user role type, for example, Assignee, Owner, or Reviewer, you can use the
following script API with the calendar task:
• submit(): boolean
• decline(): boolean
• approve(): boolean
• reject(): boolean
To use this script API, you first have to convert the generic calendar task, a CalendarTask object,
to the target type of the calendar task, that is, a General Task, a Review Task, or a Composite
Task (a CalendarGeneralTask, CalendarReviewTask, or CalendarCompositeTask object,
respectively) using the cast() script API method.
Example:
With the following sample code snippet, you can cast a calendar task a Composite Task:
var calendarTask = CalendarIntegration_1.getCurrentTask();
if (calendarTask.getType() === CalendarTaskType.CompositeTask) {
var compositeTask = cast(Type.CalendarCompositeTask, calendarTask);
// ...
}
Note the check if the calendar task is of the correct target calendar task type before the cast
operation takes place.
Widget Concepts, Script APIs, and Usages 135
This is necessary because a user cannot perform operations on a calendar task, for example,
submit, decline, approve, or reject, if an analytic application isn’t associated with a calendar task
through a working file.
Example:
With the following sample code snippet, you can log the current task to the browser console:
var calendarTask = CalendarIntegration_1.getCurrentTask();
console.log(calendarTask);
Example:
With the following sample code snippet, you can print the status of the calendar task displayed in
the UI (see screenshot below) to the browser console:
var calendarTaskStatus = CalendarIntegration_1.getCurrentTask().getStatus();
switch (calendarTaskStatus) {
case CalendarTaskStatus.Accomplished:
console.log("Accomplished");
break;
case CalendarTaskStatus.InProgress:
console.log("In Progress");
break;
case CalendarTaskStatus.Canceled:
console.log("Canceled");
break;
case CalendarTaskStatus.OnHold:
console.log("OnHold");
break;
case CalendarTaskStatus.Open:
console.log("Open");
break;
}
Widget Concepts, Script APIs, and Usages 136
Example:
With the following sample code snippet, you can check if the current calendar task is a General
Task:
var calendarTaskType = CalendarIntegration_1.getCurrentTask().getType();
if (calendarTaskType === CalendarTaskType.GeneralTask) {
console.log("This is a general task.");
}
• CalendarTaskUserRoleType.Owner
• CalendarTaskUserRoleType.Assignee
The script API returns true if the currently logged-in user has the specified user role, and false
otherwise.
Example:
With the following sample code snippet, you can check if the currently logged-in user is a reviewer
of the currently loaded calendar task:
var calendarTask = CalendarIntegration_1.getCurrentTask();
var isReviewer = calendarTask.hasUserRole(CalendarTaskUserRoleType.Reviewer);
console.log(isReviewer);
• CalendarCompositeTask.submit(): boolean
Example:
With the following sample code snippet, you can submit a Composite Task:
var calendarTask = CalendarIntegration_1.getCurrentTask();
var compositeTask = cast(Type.CalendarCompositeTask, calendarTask);
var isSubmitted = compositeTask.submit();
if (isSubmitted) {
console.log("You have successfully submitted your task.");
} else {
console.log("Sorry, something went wrong.");
}
This script API works like the Submit action on the side panel of the calendar.
• CalendarCompositeTask.decline(): boolean
Example:
With the following sample code snippet, you can decline a Composite Task:
var calendarTask = CalendarIntegration_1.getCurrentTask();
var compositeTask = cast(Type.CalendarCompositeTask, calendarTask);
var isDeclined = compositeTask.decline();
if (isDeclined) {
console.log("You have successfully declined your task.");
} else {
console.log("Sorry, something went wrong.");
}
This script API works like the Decline action on the side panel of the calendar.
• CalendarCompositeTask.approve(): boolean
Example:
With the following sample code snippet, you can approve a Review Task:
var calendarTask = CalendarIntegration_1.getCurrentTask();
var reviewTask = cast(Type.CalendarReviewTask, calendarTask);
Widget Concepts, Script APIs, and Usages 138
• CalendarCompositeTask.reject(): boolean
Example:
With the following sample code snippet, you can reject a Composite Task:
var calendarTask = CalendarIntegration_1.getCurrentTask();
var compositeTask = cast(Type.CalendarCompositeTask, calendarTask);
var isRejected = compositeTask.reject();
if (isRejected) {
console.log("You have successfully rejected your task.");
} else {
console.log("Sorry, something went wrong.");
}
This script API works like the Reject action on the side panel of the calendar.
Example:
With the following sample code snippet, you can log the result of the getType() script API method
to the browser console for any task:
var task = CalendarIntegration_1.getCalendarTaskById("taskId");
if (task.getType() === CalendarTaskType.CompositeTask) {
console.log("This is a composite task");
} else if (task.getType() === CalendarTaskType.GeneralTask) {
console.log("This is a general task");
} else if (task.getType() === CalendarTaskType.ReviewTask) {
console.log("This is a review task");
}
Widget Concepts, Script APIs, and Usages 139
Example:
With the following sample code snippet, you can activate a task and issue a notification:
var task = CalendarIntegration_1.getCalendarTaskById("taskId");
task.activate(true);
This script API works like the Activate or Activate and Notify action from the calendar view.
Note: Recurring composite tasks are currently not delivered as part of the result.
Example:
With the following sample code snippet, you can retrieve all tasks for which the opened analytic
application is a working file:
var allTasks = CalendarIntegration_1.getRelatedTaskIds();
console.log(allTasks);
When creating a composite task, you can choose a name, a start date, and a due date, which are
mandatory properties. You can choose between an analytic application or a story when specifying
the work file.
Example:
With the following sample code snippet, you can create a Composite Task is created that starts
now and ends on March 16, 2022. There is one work file belonging to this composite task, the
currently opened application. One assignee is defined, the currently logged-in user. Additionally,
a reviewer is specified, in one review round:
var newTask = CalendarIntegration_1.createCompositeTask({
name: "newCompositeTask",
startDate:new Date(Date.now()),
dueDate: new Date(2022, 2, 16, 0, 0, 0),
workFiles: [{
id: Application.getInfo().id,
type: CalendarTaskWorkFileType.AnalyticApplication,
}],
assignees: [Application.getUserInfo().id],
description: "Calendar Composite Task description",
Widget Concepts, Script APIs, and Usages 140
reviewers: {
"1": ["reviewerId"],
}
}, {
autoActivate: true
});
The option autoActivate replaces the Activate the task at start date automatically option in the
user interface.
5.34.14 Using the Status Change APIs for any Calendar Composite Task
You can use the submit(), decline(), approve(), or reject() script API methods for any
composite task, given the task ID.
Example:
With the following sample code snippet, you can submit a Composite Task with a given task ID:
var task = CalendarIntegration_1.getCalendarTaskById("taskId");
if (task.getType() === CalendarTaskType.CompositeTask) {
var compositeTask = cast (Type.CalendarCompositeTask, task);
compositeTask.submit();
}
Data actions are a flexible planning tool for making structured changes to model data, including
copying data from one model to another. In analytics designer there are two ways of adding data
actions to your application:
• Using the Data Action Trigger widget and its related script API
• Using the Data Action component and its related script API
With the Data Action component and its related script API you can do the following:
• Execute the data action
• Set the value of a data action parameter
• Read the value of a data action parameter
5.35.1 Prerequisites
Before you can use a Data Action component, there have to be data actions created already.
To work with the Data Action component, you need the following permissions and roles:
• To create or edit a Data Action component, you need the permission to edit the analytic
application.
• To select a data action in the Data Action Configuration panel you need the respective
read permission for data actions.
Widget Concepts, Script APIs, and Usages 141
• To execute a data action, you need the respective execute permission for the selected
data action.
Example:
In the following example, a set of dimension member filters of dimension dimensionId is assigned
to parameter parameterId of a data action. The code collects the relevant single value and
multiple value dimension member filters into an array before assigning them to the parameter.
Exclusive filters are ignored, as they aren’t supported by the data action.
var filters = Table_1.getDataSource().getDimensionFilters(dimensionId);
var filteredMemberIds = ArrayUtils.create(Type.string);
if (!multiFilter.exclude) {
filteredMemberIds = filteredMemberIds.concat(multiFilter.values);
}
}
}
DataAction_1.setParameterValue(parameterId, {
type: DataActionParameterValueType.Member,
members: filteredMemberIds
});
Example:
In the following example, the value of parameter parameterId of a data action is printed to the
browser console. The value can be a number or an array of dimension member IDs.
var parameterValue = DataAction_1.getParameterValue(parameterId);
if (parameterValue.type === DataActionParameterValueType.Number) {
var numberParameter = cast(Type.DataActionNumberParameterValue, parameterValue);
console.log(numberParameter.value);
} else { // parameterValue.type === DataActionParameterValueType.Member
var memberParameter = cast(Type.DataActionMemberParameterValue, parameterValue);
var memberIds = memberParameter.members;
for (var i = 0; I < memberIds.length; i++) {
console.log(memberIds[i]);
}
}
Example:
In the following example, a data action is run, and a message is printed to the browser console
whether the operation was successful or not.
var response = DataAction_1.execute();
if (response.status === DataActionExecutionResponseStatus.Success) {
console.log("The execution of your data action was successful.");
} else { // response.status === DataActionExecutionResponseStatus.Error
console.log("Sorry, the execution of your data action failed.");
}
Widget Concepts, Script APIs, and Usages 143
5.36 Comments
Managing Comments
You, as an analytic application developer, can manage data cell-based comments with a script
API and specifying the comment ID or a data context (selection).
Example:
Example:
Example:
Retrieving Comments
Besides end users posting or removing comments, you, as an analytic application developer, can
read the comment information by comment ID or data context (selection) with a script API. The
comment information includes content, author, creation date, and so on.
Example:
You, as an analytic application developer, can turn on and off the display of comments via a script
API. Once the comment mode is disabled, the comments and related UI are invisible at runtime.
Widget Concepts, Script APIs, and Usages 144
Application.isCommentModeEnabled();
Application.setCommentModeEnabled(true);
Liking a Comment
You, as an analytic application developer, can like a comment with a script API. Once a comment
ID is specified, the number of likes of the related comment is updated.
Table_1.getDataSource().getComments().setCommentLiked(("28760729-2540-4227-b016-
428450515042", true);
Adding a Comment
At runtime, end users can like or delete a comment by clicking the respective icon at the top of
each comment:
Example:
In the following example, the dimension filter of dimension Location from a table is copied to a
Comment widget:
var filter = Table_1.getDataSource().getDimensionFilters("Location")[0];
if (filter.type === FilterValueType.Single) {
// Table_1 has a single filter, for example, "location=SA1"
var singleFilter = cast(Type.SingleFilterValue, filter);
CommentWidget_1.getCommentingDataSource().setDimensionFilter("Location",
singleFilter.value);
} else if (filter.type === FilterValueType.Multiple) {
// Table_1 has multiple filters, for example, "location=SA1" and "location=SA2"
var multipleFilter = cast(Type.MultipleFilterValue, filter);
CommentWidget_1.getCommentingDataSource().setDimensionFilter("Location",
multipleFilter.values);
}
5.37.1.1 Configuration
To add an Export to PDF component, click in the Scripting section of the Outline the “+” (plus)
icon next to Export to PDF. This creates the Export to PDF component and opens its Export to
PDF panel:
Widget Concepts, Script APIs, and Usages 148
Example:
In the following example, the analytic application is exported to PDF file Export1.pdf:
ExportToPDF_1.setFileName("Export1.pdf");
ExportToPDF_1.exportView();
Example:
In the following example, all tables are exported in full length (report) to file Export2.pdf (assuming
all tables have been included in the Included Widgets dialog):
ExportToPDF_1.setFileName("Export2.pdf");
ExportToPDF_1.exportReport();
Example:
In the following example, tables Table_1 and Table_2 are exported in full length (report) to file
Export3.pdf (assuming no other tables have been included in the Included Widgets dialog):
ExportToPDF_1.setFileName("Export3.pdf");
ExportToPDF_1.includeComponent(Table_1);
ExportToPDF_1.includeComponent(Table_2);
ExportToPDF_1.exportReport();
Widget Concepts, Script APIs, and Usages 151
Example:
In the following example, the application is exported to PDF file Export4.pdf. Tables Table_1 and
Table_2 are appended in full length (report) to the exported file.
ExportToPDF_1.setFileName("Export4.pdf");
ExportToPDF_1.setReportIncluded(true);
ExportToPDF_1.includeComponent(Table_1);
ExportToPDF_1.includeComponent(Table_2);
ExportToPDF_1.exportView();
5.37.2.1 Configuration
To add an Export to Excel component, click in the Scripting section of the Outline the “+” (plus)
icon next to Export to Excel. This creates the Export to Excel component and opens its Export to
Excel panel:
• Include Metadata
You can specify whether the table metadata is exported as well. Table metadata is
placed on the last Excel sheet.
• Include Number Formatting
You can specify whether the data values are exported with the scaling, units, and
currencies as defined in the model and application.
• Keep Hierarchical Indentation
You can specify whether to keep the hierarchical indentation of the data labels.
Example:
In the following example, tables Table_1 and Table_2 are exported to Excel file Export.xlsx,
including their metadata. In the exported Excel file, Table_1 is placed on the first sheet, Table_2
on the second sheet, and the metadata on the third sheet.
ExportToExcel_1.setFileName("Export.xlsx");
ExportToExcel_1.setWidget([Table_1, Table_2]);
ExportToExcel_1.setAppendixIncluded(true);
ExportToExcel_1.setExportFormattedValues(true);
ExportToExcel_1.setIndentedHierarchy(true);
ExportToExcel_1.exportReport();
5.37.3.1 Configuration
To add an Export to CSV component, click in the Scripting section of the Outline the “+” (plus)
icon next to Export to CSV. This creates the Export to CSV component and opens its Export to
CSV panel:
Widget Concepts, Script APIs, and Usages 153
Example:
In the following example, table Table_1 is completely exported to CSV file Export.csv:
ExportToCsv_1.setFileName("Export.csv");
ExportToCsv_1.setWidget(Table_1);
ExportToCsv_1.setExportFormattedValues(false);
ExportToCsv_1.setHierarchyLevelsInIndividualCells(true);
ExportToCsv_1.setScope(ExportScope.All);
ExportToCsv_1.exportReport();
5.38.1 Chart
In class Chart the following methods were deprecated and replaced:
Deprecated
addMeasure(measure: string | MeasureInfo, feed: Feed, position?: integer): void
Replaced by
addMember(feed: Feed, structureMember: string | MeasureInfo | MemberInfo,
position?: integer): void
Deprecated
getMeasures(feed: Feed): string[]
Replaced by
getMembers(feed: Feed): string[]
Deprecated
removeMeasure(measure: string | MeasureInfo, feed: Feed): void
Replaced by
removeMember(feed: Feed, member: string | MeasureInfo | MemberInfo): void
Deprecated
setAdditionalMeasures(additionalMeasures: string[] | MeasureInfo[]): void
Replaced by
setAdditionalStructureDimensionMembers(structureDimension: string | DimensionInfo,
structureDimensionMembers: string[] | MemberInfo[]| MeasureInfo[]): void
Widget Concepts, Script APIs, and Usages 155
Deprecated
setIncludedMeasures(includedMeasures: string[] | MeasureInfo[]): void
Replaced by
setIncludedDataColumns(structureMembers: string[] | MemberInfo[] | MeasureInfo[]):
void
Deprecated
setIncludedDimensions(includedDimensions: string[] | DimensionInfo[]): void
Replaced by
setIncludedDimensionColumns(includedDimensions: string[] | DimensionInfo[]): void
Deprecated
setTooltipMeasureIncluded(included: boolean): void
Replaced by
setTooltipFeedsIncluded(included: boolean): void
The new story is created in a new browser page. The settings and data state (that is, filters and
so on) will be carried over as well.
Example:
In the following example, new stories are created from existing widgets:
Chart_1.createStoryFromWidget();
Table_1.createStoryFromWidget();
RVisualization_1.createStoryFromWidget();
GeoMap_1.createStoryFromWidget();
Basically, the script API can be used in two ways: open the analytic application or a page of a
story directly or open an URL.
The script API takes the uuid of an analytic application or a page in a story and opens the expected
application or page in a new tab if parameter “newTab” is set to true.
Widget Concepts, Script APIs, and Usages 157
NavigationUtils.openStory("story_uuid", "page_uuid",
[UrlParameter.create("p_script_var_1", "123"),
UrlParameter.create("p_script_var_2", "Value with Spaces")]);
NavigationUtils.openApplication("application_uuid", true);
Incidentally, when using openStory(), then the page_uuid is simply the page number as a string.
For example, the page_uuid of the first page of a multi-page story is "1".
Open URL
The user can also choose to open an URL, which is a story or analytic application URL, or even
a general external URL. The URL can be opened in a new tab or in a browser page that is already
open.
var storyURL = createStoryUrl("story_uuid", "page_uuid",
UrlParameter.create("p_script_var_1", "123"));
var appURL = createApplicationUrl("application_uuid");
openUrl(storyURL, true);
openUrl(appURL, true);
The user can also choose to open a data analyzer to analyze data of a data source. The user can
pass the name of the connection, the name of the data source, and URL parameters, if necessary.
The data analyzer opens in a new tab or in a browser page that is already open.
NavigationUtils.openDataAnalyzer("myconnection", "mydataSourceName",
UrlParameter.create("P_script_var_1", "123"), true);
Check option allow-popups when the Web Page widget embeds another analytic application or
story that has visualizations connecting to SAP HANA or SAP BW Live data.
Note: If the live data connection type is Tunnel, you don’t need to check option allow-popups.
Widget Concepts, Script APIs, and Usages 158
In your analytic application you can use as many Web Page widgets as you like to embed other
analytic applications or stories. However, check for potential issues such as performance and
security considerations in the specification of the <iframe> HTML element.
Known restriction
Embedding an analytic application via a Web Page widget into stories or the digital boardroom
isn’t supported.
You, as an analytic application developer, can open these dialogs at runtime with a scripting API.
You, as an end user, can also add and edit URL parameters of the default link. The resulting
updated default link is displayed in the Default Link text field.
Widget Concepts, Script APIs, and Usages 159
• view_id
Note: The overall length of the default link is limited to 5000 characters. The length of the URL
parameters contained in the default link is limited to 4000 characters.
Example:
Example:
https://www.example.com/sap/.../7C51AB04E6C74174D9ACDEF133B09D40?mode=embed
Example:
If you add the URL parameter ?p_gvar1=123 to the custom link at runtime as follows:
https://www.example.com/link/app1?p_gvar1=123
Example:
If you add the URL parameters ?p_gvar1=123&mode=view to the custom link at runtime as follows:
https://www.example.com/link/app1?p_gvar1=123&mode=view
https://www.sampleexample.com/sap/.../7C51AB04E6C74174D9ACDEF133B09D40?mode=view&p_gv
ar1=123
Note: You can share only personal bookmarks of the currently open analytic application.
Typical Patterns and Best Practices 161
To achieve this, we will add an icon that represents a Chart and another that represents a Table.
Then, we will write scripts for each of the images/icon we added to make it so that when we click
on the Chart icon, the chart will appear, and the Table will be invisible, and vice versa.
Our default setting, shown when the application is first run, will be to make the Table visible (and
the Chart invisible).
The result will look like this when we first run the application:
And if we click on the image, we will get the Chart and the image will change its look to a
Table icon and if we select it we come back to the view of the previous screenshot:
Prerequisites for this use case is having already added a Table and a Chart to your Canvas.
Select, for example, the model BestRun_Advanced as data source.
icon to
Switch_to_Table_display and the
Chart.setVisible(true);
Table.setVisible(false);
Switch_to_Table_display.setVisible(true);
Switch_to_Chart_display.setVisible(false);
on the button.
Chart.setVisible(false);
Table.setVisible(true);
Switch_to_Table_display.setVisible(false);
Switch_to_Chart_display.setVisible(true);
Typical Patterns and Best Practices 165
In the Dropdown widget, we will load all the measures from our data set and set the default filtering
measure of the table to “Gross Margin Plan”.
When another measure is selected, the filter is applied to the Table as well as the Chart (You can
go from the Table to the Chart and vice versa using the and the icons, respectively.)
The result will look like this when we run the application:
And if we click on the Dropdown, we will get all the measures with which we can filter the results
of the Table or the Chart:
Typical Patterns and Best Practices 166
Prerequisites for this use case is having already added a table and a chart to your Canvas. To
have all the functionalities in this use case, first go through the Switching Between Chart and
Table exercise.
[Account_BestRunJ_sold].[parentId].&[Gross_MarginActual]
Typical Patterns and Best Practices 169
Table.getDataSource().setDimensionFilter("Account_BestRunJ_so
ld",selectedId);
Chart.addMeasure(selectedId, Feed.ValueAxis);
if (measures.length > 0) {
for (var i = 0; i < measures.length; i++){
// Measure
Dropdown_Measures.addItem(measures[i].id,
measures[i].description);
if (selectedKey === "" && i === 0) {
selectedKey = measures[i].id;
Dropdown_Measures.setSelectedKey(selectedKey);
console.log(["selectedKey ", selectedKey]);
}
console.log(["CurrentMeasure ", measures]);
}
}
Utils.setMeasureFilter(selectedKey);
Typical Patterns and Best Practices 172
Now let’s see how it looks like. Application when it’s first run:
Unlike a Dropdown, the Checkbox Group allows using multiple measures as filters. In this use
case, we will add a Checkbox Group widget where we will list all the measures in our data set.
On top of that, there will be the following three buttons;
• “Set selected” to filter the Table and Chart using the checked measures in the Checkbox
• “Remove all” to remove all the selected filters
• “Set all” to display all the available measures in our Table/Chart
The result will look like this when we run the application:
Typical Patterns and Best Practices 173
Prerequisites for this use case is having already added a table and a chart to your Canvas. To
have all the functionalities in this use case, first go through the Switching Between Chart and
Table exercise.
Chart.removeMeasure(CurrentMeasureFilterSelection[i]
, Feed.ValueAxis);
}
}
// add Measures
Table.getDataSource().setDimensionFilter("Account_Be
stRunJ_sold",selectedIds);
for (i = 0; i < selectedIds.length; i++) {
Chart.addMeasure(selectedIds[i], Feed.ValueAxis);
}
// save the current selection into global variable
CurrentMeasureFilterSelection = selectedIds;
similarly select .
Utils.setMeasureFilter(CheckboxGroup_Measures.getSel
ectedKeys());
Typical Patterns and Best Practices 180
CheckboxGroup_Measures.setSelectedKeys(AllMeasures);
Utils.setMeasureFilter(AllMeasures);
if (measures.length > 0) {
for (var i = 0; i < measures.length; i++) {
// add the Measure to checkbox group
CheckboxGroup_Measures.addItem(measures[i].id,measur
es[i].description);
//add the measure to the selecedKeys
selectedKeys.push(measures[i].id);
CheckboxGroup_Measures.setSelectedKeys(selectedKeys)
;
console.log(["CurrentMeasure ", measures]);
}
}
Now let’s see how it looks like. Application when it’s first run:
Click on Run Analytic Application in the
upper right side of the page and the result
should look something like this:
Let us only select a few measures and see Table after clicking on “Remove all”:
how the Table will change.
In the screenshot on the right, 4 measures
are chosen (Gross Margin Plan, Quantity
Sold, Original Sales Price abs Dev, and
Discount).
After selecting the measures, click on “set
selected” to update the Table/Chart with
your chosen measures.
Instead of loading all the dimensions in our data set into a Checkbox group or a Dropdown widget,
in this use case, we will select specific dimensions to load into a Filter Line.
Typical Patterns and Best Practices 183
Unlike other data-bound widgets (such as Table or Chart), R Visualization can add multiple input
data models. To support R Visualization in Filter Line, one Dropdown list is added to select the
connected input data.
After the user selects an input data model of the R Visualization widget, the Filter Line can support
R Visualization just like other widgets.
After loading the desired dimensions into our Filter Line, we will connect it to our Table/Chart/R
Visualization so that the data is filtered using the selected filter.
To use the Filter Line after running the application, simply click on the Filter Line icon and select
the dimension you want to use to filter your data.
The result will look like this when we run the application:
And this is how it will look like when we click on our Filter Line widget:
Typical Patterns and Best Practices 184
Prerequisites for this use case is having already added a Table and a Chart to your Canvas. To
have all the functionalities in this use case, first go through the Switching Between Chart and
Table exercise.
the and
respectively (refer to the
Switching Between Chart and
Table Exercise).
console.log('OnResultChanged');
Chart.getDataSource().copyDimensionFilterFrom(Table.getDataSou
rce(), "Location_4nm2e04531");
Chart.getDataSource().copyDimensionFilterFrom(Table.getDataSou
rce(), "Product_3e315003an");
Chart.getDataSource().copyDimensionFilterFrom(Table.getDataSou
rce(), "Sales_Manager__5w3m5d06b5");
Chart.getDataSource().copyDimensionFilterFrom(Table.getDataSou
rce(), "Store_3z2g5g06m4.Store_GEOID");
Typical Patterns and Best Practices 187
We will add two Dropdown lists, one for filtering dimensions and the other for filtering hierarchies
and depending on what dimension we choose to filter on, the Dropdown List for the hierarchy
filters will change.
There is always one consistent filter for hierarchies which is Flat Presentation and according to
our chosen dimension, we might either only have that one or have more options.
For example, if we are filtering on Location, we have two choices for hierarchies: Flat Presentation
and according to States, however, if we are filtering on Product, we have Flat Presentation,
Category, or ABC (this one categorizes the dimension as “worst-selling”, “medium-selling”, or
“best-selling”), and if we are filtering on Store or Sales Manager, our only option is Flat
Presentation.
The different filters can be chosen by simply selecting them from the Dropdown lists we added.
The result will look like this when we run the application:
Prerequisites for this use case is having already added a Table and a Chart to your Canvas. To
have all the functionalities in this use case, first go through the Switching Between Chart and
Table exercise.
Typical Patterns and Best Practices 189
Location_4nm2e04531 Location
Product_3e315003an Product
Store_3z2g5g06m4 Store
// Table
Table.removeDimension(CurrentDimension);
Table.addDimensionToRows(sel);
//Chart
Chart.removeDimension(CurrentDimension, Feed.CategoryAxis);
Chart.addDimension(sel, Feed.CategoryAxis);
// loop
for (var i = 0; i < hierarchies.length; i++) {
if (hierarchies[i].id === '__FLAT__') {
Dropdown_Hierarchies.addItem(hierarchies[i].id, 'Flat
Presentation');
}
else {
Typical Patterns and Best Practices 193
Dropdown_Hierarchies.addItem(hierarchies[i].id,
hierarchies[i].description);
if (flag === true) {
var hierarchy = hierarchies[i].id;
flag = false;
}
}
}
// write hierarchy information to browser console
console.log(['Hierarchy: ', hierarchy]);
console.log(['Current Dimension: ', CurrentDimension]);
// Table
Table.getDataSource().setHierarchy(CurrentDimension,
'__FLAT__');
// Chart
Chart.getDataSource().setHierarchy(CurrentDimension,
'__FLAT__');
// loop
for (var i = 0; i < hierarchies.length; i++) {
if (hierarchies[i].id === '__FLAT__') {
Dropdown_Hierarchies.addItem(hierarchies[i].id, 'Flat
Presentation');
}
else {
Dropdown_Hierarchies.addItem(hierarchies[i].id,
hierarchies[i].description);
if (flag === true) {
var hierarchy = hierarchies[i].id;
flag = false;
}
}
}
// write hierarchy information to browser console
console.log(['Hierarchy: ', hierarchy]);
console.log(['Current Dimension: ', CurrentDimension]);
//Table
Table.getDataSource().setHierarchy(CurrentDimension,
'__FLAT__');
//Chart
Chart.getDataSource().setHierarchy(CurrentDimension,
'__FLAT__');
Typical Patterns and Best Practices 195
The user can select which measures they would like displayed in the Table through the Measures
Checkbox and then through another Checkbox, they could decide which dimensions they want
displayed on the columns or the rows of the Table.
The application also makes it easier for the user to select all or remove all measures by adding
buttons specifically for that purpose.
They can also remove the dimensions that they added to the columns and rows and are able to
choose to add them again afterwards.
The result will look like this when we run the application:
Typical Patterns and Best Practices 196
This application assumes that there already is a Table in your Canvas. To match the scripts in
the application it is recommended to rename the widget to Table.
Typical Patterns and Best Practices 197
There, enter
“CheckboxGroup_Measures” as the
Name and choose Vertical Layout
as the Display Option.
Typical Patterns and Best Practices 198
CheckboxGroup_Columns.removeAllItems();
CheckboxGroup_Rows.removeAllItems();
CheckboxGroup_Free.removeAllItems();
CurrentDimensionColumn = ArrayUtils.create(Type.string);
CurrentDimensionRows = ArrayUtils.create(Type.string);
console.log(["CurrentDimensionColumn should empty",
CurrentDimensionColumn.slice()]);
console.log(["CurrentDimensionRows should empty",
CurrentDimensionRows.slice()]);
// Dimension in Columns
var dimCol = Table.getDimensionsOnColumns();
if (dimCol.length > 0) {
for (var i = 0; i < dimCol.length; i++) {
CurrentDimensionColumn.push(dimCol[i]);
console.log(["CurrentDimensionColumn ", dimCol[i]]);
}
}
Typical Patterns and Best Practices 210
// Dimension in Rows
var dimRows = Table.getDimensionsOnRows();
if (dimRows.length > 0) {
for (i = 0; i < dimRows.length; i++) {
CurrentDimensionRows.push(dimRows[i]);
console.log(["CurrentDimensionRows ", dimRows[i]]);
}
}
CheckboxGroup_AllDimensions.setSelectedKeys([AllDimension
s[i]]);
var dimdesc =
CheckboxGroup_AllDimensions.getSelectedTexts();
CheckboxGroup_Free.addItem(AllDimensions[i],
dimdesc[0]);
console.log(["AllDimensions",AllDimensions[i],
dimdesc[0]]);
}
}
}
console.log(["CurrentDimensionColumn",
CurrentDimensionColumn]);
console.log(["CurrentDimensionRows",
CurrentDimensionRows]);
CheckboxGroup_Free.setSelectedKeys([CurrentDimensionRows[
i]]);
dimdesc = CheckboxGroup_Free.getSelectedTexts();
CheckboxGroup_Rows.addItem(CurrentDimensionRows[i],
dimdesc[0]);
CheckboxGroup_Free.removeItem(CurrentDimensionRows[i]);
}
}
}
if (CurrentDimensionColumn.length > 0) {
for (i = 0; i < CurrentDimensionColumn.length; i++) {
if (CurrentDimensionColumn[i] !== "") {
CheckboxGroup_Free.setSelectedKeys([CurrentDimensionColum
n[i]]);
dimdesc = CheckboxGroup_Free.getSelectedTexts();
CheckboxGroup_Columns.addItem(CurrentDimensionColumn[i],
dimdesc[0]);
CheckboxGroup_Free.removeItem(CurrentDimensionColumn[i]);
}
}
}
Typical Patterns and Best Practices 211
Utils.setMeasureFilter(CheckboxGroup_Measures.getSelected
Keys());
CheckboxGroup_Measures.setSelectedKeys(AllMeasures);
Utils.setMeasureFilter(AllMeasures);
Utils.setDimensionCheckboxes();
Utils.setDimensionCheckboxes();
Typical Patterns and Best Practices 213
Utils.setDimensionCheckboxes();
Utils.setMeasureFilter(selectedKeys);
Utils.setDimensionCheckboxes();
Typical Patterns and Best Practices 215
In this use case, we want to be able to filter our table and chart according to certain measure
groups of our data set. Here, Gross Margin, Discount, Quantity Sold, and Original Sales Price are
the options.
These measure groups are going to be selected from a Dropdown list in our Canvas.
Afterwards, we will use the popup widget to switch between Table and Chart using a Radio Button
Group and give the user the ability to control the measures (Actual, Plan, Absolute, and % of
Deviation) of the measure groups using a Checkbox Group widget.
The result will look like this when we run the application:
Typical Patterns and Best Practices 217
And when the Settings button is clicked, the application will display the popup with the
settings that the user can change:
Prerequisites for this use case is having already added a table and a chart to your Canvas. To
have all the functionalities in this use case, first go through the Switching Between Chart and
Table exercise.
Typical Patterns and Best Practices 218
There, enter
“Dropdown_MeasureGroup” as the
Name.
Typical Patterns and Best Practices 219
There, enter
“CheckboxGroup_Measure_Selection”
as the Name and select “Vertical
Layout” as the Display Option.
Actual Actual
Plan Plan
_Abs Absolute
_Percent % Deviation
Popup_Settings.open();
Table.getDataSource().setDimensionFilter("Account_Bes
tRunJ_sold", selectedId);
Chart.addMeasure(selectedId, Feed.ValueAxis);
Typical Patterns and Best Practices 229
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Gross_MarginActual]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Gross_MarginPlan]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Gross_Margin_Abs]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Gross_Margin_Percent]", Feed.ValueAxis);
}
else if (CurrentMeasureGroup === 'Discount') {
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[DiscountActual]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[DiscountPlan]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Discount_Abs]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Discount_Percent]", Feed.ValueAxis);
}
else if (CurrentMeasureGroup === 'Quantity_Sold') {
Typical Patterns and Best Practices 230
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Quantity_soldActual]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Quantity_soldPlan]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Quantity_sold_Abs]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Quantity_sold_Percent]", Feed.ValueAxis);
}
else if (CurrentMeasureGroup ===
'Original_Sales_Price') {
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Original_Sales_PriceActual]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Original_Sales_PricePlan]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Original_Sales_Price_Abs]", Feed.ValueAxis);
Chart.removeMeasure("[Account_BestRunJ_sold].[parentI
d].&[Original_Sales_Price_Percent]", Feed.ValueAxis);
}
Chart.removeMeasure(CurrentMeasureFilterSelectionPopu
p[i], Feed.ValueAxis);
}
// help variables
var Filter_Pattern_1 =
"[Account_BestRunJ_sold].[parentId].&[";
var Filter_Pattern_2 = "]";
var Filter_Area = ArrayUtils.create(Type.string);
CurrentMeasureSelection = Selected_Measures;
Chart.removeMeasure(CurrentMeasureFilterSelectionPopu
p[i], Feed.ValueAxis);
}
// help variables
var Filter_Pattern_1 =
"[Account_BestRunJ_sold].[parentId].&[";
var Filter_Pattern_2 = "]";
var Filter_Area = ArrayUtils.create(Type.string);
Table.getDataSource().removeDimensionFilter("Account_
BestRunJ_sold");
Table.getDataSource().setDimensionFilter("Account_Bes
tRunJ_sold", Filter_Area);
In a Table, a user will be able to select a measure cell, a dimension cell, or a data cell. Each will
open a popup window that displays information about the selected element in a trend chart.
In the Chart, a user will be able to select a dimension cell and a measure/dimension chart bar (for
example, Gross Margin Plan for Lemonade).
There are also two Dropdown lists, one for dimensions and the other for hierarchies. The list of
dimensions let the user choose which dimension filter they want to use on the Table/Chart. In this
use case, we have chosen 4 dimensions; Location, Product, Store, and Sales Manager.
The second Dropdown list displays the available hierarchies that can be used to change how the
data is displayed.
Note: In this example, only single selection is supported for the Table and Chart.
The result will look like this when we run the application:
And when a cell is chosen, a popup window like the one in the screenshot will appear (In this
screenshot, the dimension cell of Los Angeles was clicked on in the Table):
Typical Patterns and Best Practices 236
Prerequisites for this use case is having already added a functioning Chart and Table to your
Canvas. To have all the functionalities in this use case, first go through the Switching Between
Chart and Table exercise.
It is recommended to use the same names as that exercise for the Chart and Table Chart so that
the scripts in this use case don’t have to be altered.
// loop
for (var i = 0; i < hierarchies.length; i++) {
if (hierarchies[i].id === '__FLAT__') {
Dropdown_Hierarchies.addItem(hierarchies[i].id,
'Flat Presentation');
}
else {
Dropdown_Hierarchies.addItem(hierarchies[i].id,
hierarchies[i].description);
if (flag === true) {
var hierarchy = hierarchies[i].id;
flag = false;
}
}
}
// write hierarchy information to browser console
console.log(['Hierarchy: ', hierarchy]);
console.log(['Current Dimension: ',
CurrentDimension]);
Details_Chart.removeMeasure(CurrentMeasures[i],
Feed.ValueAxis);
Details_Chart.addMeasure(memberId,
Feed.ValueAxis);
}
//Details_Chart.addMeasure(memberId,
Feed.ValueAxis);
CurrentDetailsMeasures.push(memberId);
Typical Patterns and Best Practices 248
Popup_show = true;
}
// Dimension
else {
console.log(['Selection Dimension: ',
dimensionId]);
console.log(['Selection Member: ', memberId]);
Details_Chart.getDataSource().setDimensionFilter(dime
nsionId, memberId);
Popup_show = true;
}
}
}
if (Popup_show === true) {
Popup_Details.open();
}
if (sel.length > 0) {
Details_Chart.getDataSource().removeDimensionFilter(C
urrentDimension);
Details_Chart.addMeasure(memberId,
Feed.ValueAxis);
CurrentDetailsMeasures.push(memberId);
Popup_show = true;
}
Typical Patterns and Best Practices 250
// Dimension
else {
console.log(['Selection Dimension: ',
dimensionId]);
console.log(['Selection Member: ',
memberId]);
Details_Chart.getDataSource().setDimensionFilter(dime
nsionId, memberId);
Popup_show = true;
}
}
}
}
Details_Chart.removeMeasure(CurrentDetailsMeasures[i]
, Feed.ValueAxis);
}
CurrentDetailsMeasures =
ArrayUtils.create(Type.string);
// loop
for (var i = 0; i < hierarchies.length; i++) {
if (hierarchies[i].id === '__FLAT__') {
Dropdown_Hierarchies.addItem(hierarchies[i].id,
'Flat Presentation');
}
else {
Dropdown_Hierarchies.addItem(hierarchies[i].id,
hierarchies[i].description);
if (flag === true) {
var hierarchy = hierarchies[i].id;
flag = false;
}
}
}
// write hierarchy information to browser console
console.log(['Hierarchy: ', hierarchy]);
console.log(['Current Dimension: ',
CurrentDimension]);
//Table
Table.getDataSource().setHierarchy(CurrentDimension,
'__FLAT__');
//Chart
Chart.getDataSource().setHierarchy(CurrentDimension,
'__FLAT__');
Typical Patterns and Best Practices 253
//Details_Chart
Details_Chart.getDataSource().setHierarchy(CurrentDim
ension, '__FLAT__');
In the Canvas, we will add a Table with our top 10 customers as well as a Chart with the
complaints of the customers. Other than that, we will have two R Visualization widgets through
which we will create word clouds that change the size of the words displayed according to the
frequency with which they appear in the data set.
Further functionalities in this application include how to filter widgets according to a selected
element of a Table and how we can change the color of the word clouds through external input
(in this use case, it is achieved through a Radio Button Group that has a script that passes the
value to the R widgets.)
And lastly, the filtering of all the widgets in the Canvas using Radio Button Groups will be explored
(here, we will filter according to Regions and according to the selected Region, several countries
from that Region will be displayed in another Radio Button Group (Country) for further filtering of
the widgets).
The result will look like this when we run the application:
There are no prerequisites for this use case. You can start with a new application.
It is recommended to use the same names as that exercise for the used widgets so that the scripts
in this use case don’t have to be altered.
There, enter
“RadioButtonGroup_Region” as the
Name, choose Vertical Layout as the
Display Option, and toggle the Label
Text button to enable it and write
“Region” as the Label Text.
Typical Patterns and Best Practices 259
REGION01 LATAM
REGION02 EMEA
REGION03 NA
REGION04 APJ
There, enter
“RadioButtonGroup_Country” as the
Name, choose Vertical Layout as the
Display Option, and toggle the Label
Text button to enable it and write
“Country” as the Label Text.
ALL All
# load package
library(wordcloud)
# get words
words <- BestRunBike_Customer_Complaint$`Complaint
Category`
# get frequency
frequency <- BestRunBike_Customer_Complaint$Count
if (exists("colorValue")) {
myColor <- colorValue
} else {
myColor <- "Oranges"
}
# generate word cloud
wordcloud(words, frequency, scale = c(4, 1),
rot.per=0.2, colors=brewer.pal(8, myColor))
# load package
library(wordcloud)
# get words
words <- BestRunBike_Customer_Complaint$`Complaint
Category`
# get frequency
frequency <- BestRunBike_Customer_Complaint$Count
if (exists("colValue")) {
myColor <- colValue
} else {
myColor <- "Oranges"
}
# generate word cloud
wordcloud(words, frequency, scale = c(4, 1),
rot.per=0.2, colors=brewer.pal(8, myColor))
if (sel.length > 0) {
var selection = sel[0];
console.log(['Selection [0] : ', selection]);
Chart_Complaints.getDataSource().setDimensionFilter(dim
ensionId, memberId);
RVisualization_WordCloud_2018.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().setDimensionFilte
r(dimensionId, memberId);
RVisualization_WordCloud_2019.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().setDimensionFilte
r(dimensionId, memberId);
Hidden_DropDown_Customer.setSelectedKey(memberId);
var text =
Hidden_DropDown_Customer.getSelectedText();
Title_Complaints.applyText("Complaints for Customer
" + text);
}
}
RadioButtonGroup_Country.removeAllItems();
RadioButtonGroup_Country.addItem("ALL", "All");
RadioButtonGroup_Country.setSelectedKey("ALL");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY011]", "Mexico");
} else if (sel === "REGION02") {
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY021]", "Dubai");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY022]", "Germany");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY023]", "Great Britian");
} else if (sel === "REGION03") {
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY031]", "USA East");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY032]", "USA West");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY033]", "Canada");
} else if (sel === "REGION04") {
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY041]", "India");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY042]", "China");
RadioButtonGroup_Country.addItem("[Country].[Region].&[
COUNTRY043]", "Australia");
}
Table_Customer.getDataSource().setDimensionFilter("Regi
on",sel);
Chart_Complaints.getDataSource().setDimensionFilter("Re
gion",sel);
RVisualization_WordCloud_2018.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().setDimensionFilte
r("Region",sel);
RVisualization_WordCloud_2019.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().setDimensionFilte
r("Region",sel);
Table_Customer.getDataSource().removeDimensionFilter("C
ountry");
Typical Patterns and Best Practices 275
Chart_Complaints.getDataSource().removeDimensionFilter(
"Country");
RVisualization_WordCloud_2018.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().removeDimensionFi
lter("Country");
RVisualization_WordCloud_2019.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().removeDimensionFi
lter("Country");
Table_Customer.getDataSource().removeDimensionFilter("C
ountry");
Chart_Complaints.getDataSource().removeDimensionFilter(
"Country");
RVisualization_WordCloud_2018.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().removeDimensionFi
lter("Country");
RVisualization_WordCloud_2019.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().removeDimensionFi
lter("Country");
} else {
Table_Customer.getDataSource().setDimensionFilter("Coun
try", sel);
Chart_Complaints.getDataSource().setDimensionFilter("Co
untry", sel);
RVisualization_WordCloud_2018.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().setDimensionFilte
r("Country", cloud_sel);
RVisualization_WordCloud_2019.getDataFrame("BestRunBike
_Customer_Complaint").getDataSource().setDimensionFilte
r("Country", cloud_sel);
}
Typical Patterns and Best Practices 276
RVisualization_WordCloud_2018.getInputParameters().setS
tring("colorValue", sel);
RVisualization_WordCloud_2019.getInputParameters().setS
tring("colValue", sel);
var list =
Table_Customer.getDataSource().getMembers("Customer_",
1000);
if (list.length !== 0) {
for (var i = 0; i < list.length; i++) {
console.log(['List Dimension: ', i ,
list[i].displayId]);
console.log(['List Description: ', i ,
list[i].description]);
console.log(['List Member: ', i , list[i].id]);
Hidden_DropDown_Customer.addItem(list[i].id,
list[i].description);
}
}
Typical Patterns and Best Practices 277
If the passed value is prefixed with an asterisk (*), then the value is applied as a factor to the
present cell value. For example, applying the following script after the script above
Table_1.getPlanning().setUserInput({"sap.epm:Account":
"[sap.epm:Account].[parentId].&[TAXES]", "sap.epm:ProfitAndLoss_Version02":
"public.Actual"}, "*0.5");
Table_1.getPlanning().submitData();
results in a cell value (raw value) of 61728394.5, which is displayed as 61.73 (formatted value).
Another example shows a combination of a table with an input field. The value of the input field is
applied as the new cell value to the first selected cell of the table:
var selectedCell = Table_1.getSelections()[0];
Typical Patterns and Best Practices 278
7 Planning
And what can you not expect? In analytics designer you cannot use Input Tasks and Planning
scripting is not possible for models based on BPC Write-Back.
These icons are greyed out if no table cell with a planning model is selected.
To get the Planning Table object, use the script below. If the table has no planning model
assigned, it will return undefined.
Table.getPlanning(): Planning | undefined
Scripting will perform the same planning actions that could be done via UI. The benefits of
scripting are augmented in cases which you want to minimize the number of clicks from your user,
personalize your UI or when a special customer requirement can’t be fulfilled with standard
planning behavior.
Data can’t be changed during design time, and you can enable the usage of planning features
during runtime in two different ways:
• In the table designer UI: You can find in the Builder panel, section Properties, a box
called Planning enabled.
Planning 280
One other valuable script allows checking whether the data model is planning enabled:
isEnabled(): boolean
In the Table’s Builder panel, there are some configurations that you can do in each dimension,
and Unbooked Data might be a good choice when, for example, your Planning Data Model has
no booked data and your end users need to see which dimension members are available for
planning.
The picture below represents the scenario mentioned above to explain this feature:
In this example, the following scripting would be included on the Save Data button.
Regarding data formatting – it takes as parameter either a raw value in the user formatting setting
(“1234.567” with “.” grouping separator) or a scale in the user formatting setting (for example,
“*0.5” to divide the value by half or “*2” to double the value) – both of type string.
Planning 282
To submit the updated cell data with all the beforehand modified data and to trigger refresh of
data:
submitData(): boolean
But to make this data visible to other users, you can publish the public versions through the
following toolbar icon:
After clicking this icon, the dialog below is opened and an action can be taken per model. You
can also revert, and all data changes will be discarded.
The actions performed within this dialog can also be done via the below scripting on public
versions:
revert(): boolean
publish(): boolean
After the execution of these scripts, a message informs whether the script ended successfully or
not. These are the expected messages:
If the version was not modified before these actions are triggered, the message below should be
expected:
It is also possible to publish private versions via the two scripting options below:
publish(): boolean
publishAs(newVersionName: String, versionCategory: PlanningCategory): boolean
In the second option, a version name is given, and a new public version is created under the
informed version category.
These scripts can be very useful if your planning model is placed in a popup, for example. As the
toolbar is kept in the background Canvas, users don’t need to close the popup to then publish the
data. With scripting, you can do it directly in the popup!
Planning 285
Find in the next section more information about version category and how to create private
versions.
7.6.2 Copy
Data models with planning enabled capability have one dimension in common, the version. And
each version is classified in one of the following planning categories:
• Actual
• Planning
• Budget
• Forecast
• Rolling Forecast
PlanningCopyOptions offers you the possibility to either create a new empty version or to copy all
data from the source version. In case you want to create a private copy of any version, use the
script below:
copy(newVersionName: string, planningCopyOption: PlanningCopyOption,
versionCategory?: PlanningCategory): boolean
• Table.getPlanning().getDataLocking()
• Table.getPlanning().getDataLocking.getState()
• Table.getPlanning().getDataLocking.setState()
This check is necessary because a user can’t perform certain operations on a table, like
setState() and getState(), if the model is not data locking enabled.
In the following example, the data locking object is retrieved and printed to the console. A data
locking object is returned if data locking is enabled on the model.
var planning = Table_1.getPlanning();
console.log(planning.getDataLocking());
Note that you can also check if a model is data locking enabled in SAP Analytic Cloud by checking
the model preferences (see Figure 83).
In following example, the data locking state for a selected cell of a table is retrieved:
var selection = Table_1.getSelections()[0];
var selectionLockState =
Table_1.getPlanning().getDataLocking().getState(selection);
Planning 287
In order to create a selection on the table, you can either select the cell in the table manually or
you can create the selection string yourself in the script editor.
• DataLockingState.Restricted
• DataLockingState.Locked
• DataLockingState.Mixed
If the state of the selection can't be determined, then the method returns undefined. This occurs
if one of the following situations applies:
• The selection is invalid.
• The cell referenced by the selection is not found.
• The cell is in an unknown state.
• The cell has been created using "Add Calculation" at runtime.
If you have activated the Show Locks option for the table, then the “lock” icons will be updated
after the method has finished running.
The method returns true if the set operation was successful and false otherwise.
You can't set the data locking state on a private version. In this case, the following message is
displayed:
“You can only set data locks on public versions. Please use a public version and try again.”
• DataLockingState.Restricted
• DataLockingState.Locked
If you attempt to set the data locking state DataLockingState.Mixed, then the following message
is displayed:
“You can't set the state with the value 'mixed'. Please specify either 'open', 'restricted' or 'locked'
as value.”
The same message is displayed at runtime if you attempt to execute the script and the script fails.
If you select multiple cells and attempt to set the data locking state, the data locking state will be
applied to the first selection only.
In the following example, the data locking state is set for a selected table cell:
var selection = Table_1.getSelections()[0];
var isSetStateSuccessful =
Table_1.getPlanning().getDataLocking().setState(selection,
DataLockingState.Locked);
Planning 288
Note that if data locking is disabled for a model, all locks will be deleted. If it is turned on again
later, all members are reset to their default locking state. The same happens if the default locking
state or driving dimensions are changed.
7.8.1 BpcPlanningSequence
onBeforeExecute
onBeforeExecute(): boolean
Called when the user clicks the BPC planning sequence trigger. If the method returns true or
returns no value, then the BPC planning sequence is executed. If the method returns false, then
the BPC planning sequence is ignored.
7.8.2 DataActionTrigger
onBeforeExecute
onBeforeExecute(): boolean
Called when the user clicks the data action trigger. If the method returns true or returns no value,
then the data action is executed. If the method returns false, then the data action is ignored.
Example:
In the following example, a new planning member is added to the dimension “LOCATION” of a
planning model:
PlanningModel_1.createMembers("LOCATION", {
id: "BERLIN",
description: "Berlin"
});
Example:
In the following example, the new planning member is updated by adding a data locking owner:
PlanningModel_1.updateMembers("LOCATION", {
id: "BERLIN",
dataLockingOwners: [{
id: "ADMIN",
type: UserType.User
}]
});
Planning 289
In the following example, the description of the new planning member is printed to the browser
console:
var member = PlanningModel_1.getMember("LOCATION", "BERLIN");
console.log(member.description);
In the following example, the fifth up to the twelfth member of dimension “LOCATION” is returned:
var members = PlanningModel_1.getMembers("LOCATION", {
offset: "4",
limit: "8"
});
The first member has an offset of 0, so the fifth member has an offset of 4. Starting at this offset,
the next 8 members are returned.
The PlanningModel script API provides many more features. For more information see the online
API reference.
Note: You can add members to dimensions of type “Generic” only (see the SAP Analytics Cloud
modeler). Adding members to dimension of other types, such as, for example, “Account”,
“Version”, “Time”, or “Organization” isn’t supported.
Note: If you have added, updated, or deleted members, then call DataSource.refreshData() or
Application.refreshData() if you need the chart or table to reflect the modified members in
subsequent method calls operating on visible cells or elements of those widgets, for example,
DataSource.getPlanning().getState(), DataSource.getPlanning().setState(),
DataSource.getData(), or Planning.setUserInput().
Note: After you have added members to a very large model (with millions of members) and have
refreshed the model with Application.refreshData() or DataSource.refreshData(), it may
happen that not all added members are immediately displayed, for example, in a table associated
with the planning model. The same applies to updating members. This is because these
operations work asynchronously in the background. Repeat your refresh operation after a short
while.
responsible Responsible
Note: When you add your own properties to planning members, use a prefix to avoid name
conflicts with existing properties of planning members.
Predictive 291
8 Predictive
In analytics designer, there are several predictive features that can help you to explore the data
and gain more insights.
Basically, Time Series Forecast can be switched on and off via two ways: the entry in context
menu at both design time and runtime,
The number of periods to predict can be configured via two ways: the entry in Chart Details at
both design time and runtime,
Predictive 292
Figure 86: Time Series Chart: Select the Interested Data Point
Predictive 293
Basically, Smart Grouping can be switched on and off via two ways: by the setting in the Builder
panel at design time,
Sample Gain insights into the data demonstrates how to trigger Smart Discovery via the script
API.
var ds = Chart_Forecast.getDataSource();
var members = ds.getMembers("Product_3e315003an");
var SDsetting = SmartDiscoveryDimensionSettings.create(ds, "Product_3e315003an",
[members[1]]);
SDsetting.setIncludedDimensions(["Location_4nm2e04531", "Store_3z2g5g06m4"]);
Predictive 295
SDsetting.setIncludedMeasures(["[Account_BestRunJ_sold].[parentId].&[Gross_Margin]"
, "[Account_BestRunJ_sold].[parentId].&[Discount]"]);
SmartDiscovery.buildStory(SDsetting);
In this example, Smart Discovery is invoked via clicking “More Insights…” to discover Product
with Dark Beer as the target group. In addition, two more measures (Gross Margin and Discount)
and two more dimensions (Location and Store) are included in the analysis.
Write Analytic Design scripts to launch Search To Insight. At runtime, the analytic application user
can open the Search To Insight dialog to get deep and flexible insights of their data.
var mode = SearchToInsightDialogMode.Simple;
SearchToInsight_1.openDialog("Gross Margin by Location", mode, true, true);
You can also apply Search To Insight results to a chart using the applySearchToChart() script
API method and leverage the following variable-related script API to save variable value in a
Search To Insight component and apply to chart when calling applySearchToChart():
// get model variable
SearchToInsight_1.getVariables(modelId: string): VariableInfo[]
In this example, you can design a simple application to let application users trigger different
modes of Search To Insight for the questions they enter.
First, in addition to the scripting object SearchToInsight_1, add an input field InputField_1, a
button Button_1 and a checkbox group CheckboxGroup_1 to the Canvas as below:
Then write the following script for the button widget Button_1:
var mode = SearchToInsightDialogMode.Simple;
if (CheckboxGroup_1.getSelectedKeys().length !== 0) {
mode = SearchToInsightDialogMode.Advanced;
}
var inputText = InputField_1.getValue();
SearchToInsight_1.openDialog(inputText, mode, false, true);
Result: After you save the application and choose Run Analytic Application, application users can
either trigger simple mode by entering a question and clicking the Search To Insight button, or
trigger advanced mode by entering a question, selecting Advanced mode, then clicking the
Search To Insight button.
Example 2: Receive Question from Host HTML Page and Apply Search To Insight Results
to a Chart
In this example, you can build your own Search To Insight user interface and integrate Search To
Insight results to your own portal.
Predictive 298
First, embed your analytic application in your own portal via iFrame and maintain the
corresponding code to get the message users input in the portal and post it to the embedded
analytic application.
Then go back to the analytic application. Besides adding the scripting object SearchToInsight_1,
write the following script for the onPostMessageReceived event of the application:
SearchToInsight_1.applySearchToChart(message, Chart_1);
Chart_1.setVisible(true);
Result: After that in your own portal, users can ask a question and see the chart of the embedded
analytic application appear and display corresponding Search To Insight results.
OData 299
9 OData
Versions 1.0, 2.0, and 3.0 are released under the Microsoft Open Specification Promise.
Version 4.0 was standardized at OASIS, with a release in March 2014. In April 2015 OASIS
submitted OData v4 and OData JSON Format v4 to ISO/IEC JTC 1 for approval as an
international standard.
“The protocol enables the creation and consumption of REST APIs, which allow Web clients to
publish and edit resources, identified using URLs and defined in a data model, using simple HTTP
messages. OData shares some similarities with JDBC and with ODBC; like ODBC, OData is not
limited to relational databases.”
Source: https://en.wikipedia.org/wiki/Open_Data_Protocol
For OData, CORS should be configured on the backend analogous to an InA connection plus
support for “if-match” as allowed header.
• For actions with optional parameters of unsupported types, the parameters will not be
available but the action itself will.
• In case of bound actions, only binding on entity types (passable by key) will be
supported.
• Only the JSON format will be supported.
• Only the following system types are supported:
o SAP S/4HANA On-Premise
o SAP BW
o SAP HANA
o SAP Business and Consolidation (BPC)
• Only Direct (CORS) connections will be supported. No Path (Proxy), as this feature is
being deprecated.
Script execution will block waiting on the response of a triggered action. For now, the assumption
is that actions triggering long-running processes return quickly (although the process may not yet
be complete). So, while of course the XHR invoking the action is asynchronous, script execution
will block waiting for the response, to allow the script writer to react to the return value of the
action.
In general, actions can be bound on every type, but we support only binding on single entities.
In analytics designer, OData actions can be called from and executed in the backend system via
scripting inside an analytic application. Also, programmatic read access to OData services is
provided.
OData 301
In your analytic application in the Layout section of the Outline in the Scripting Section you can
create a new OData Service by clicking on plus.
Once you have clicked a new entry with the default name ODataService_1 will appear below the
node. You will see a context menu indicated with three points when hovering over the name,
where you do the following actions: Rename, Find References, or Delete.
At the same time the side panel opens on the right side. It opens every time you click on the
OData Service in the Outline. In the side panel you can change the name, select the System from
the list of available systems whose connections are already created in SAC under Connections,
and specify the End-Point URL of the OData Service manually.
OData 302
Note: You need to know the URL. So far there is no browse catalog implemented.
To see the metadata of the OData Service you must click the refresh button next to Metadata.
Click on Done to close the panel.
In the example you see System FUB, the End-Point URL for this OData Service and as Metadata
you got the information that this Service is based on OData Version V.4 and it has 2 Actions called
Flight/Book and CancelMyFlights.
OData 303
Now you can insert a Button Widget and change the text of the Button in the Application Design
Properties of Styling panel to Cancel Flight.
Start the script editor by clicking the icon in the quick action menu of the widget to create a
script which triggers the execution of the action in the source system.
The script editor opens. You can open it as well by hovering over the widget in the Outline and
clicking the icon.
Type in the name of the OData Service you have specified. The script editor assists you with code
completion and value help wherever possible when you click CTRL+Space.
Now you can insert another button, rename the text to Book Flight in the Styling panel and open
the script editor. The BookFlight Action is a bound action which is much more complex than the
first one.
Congratulations. You finished the second more complex OData action and now you can run your
application and book and cancel a flight for the selected values.
You can enhance your application and start using other script methods to fill the parameter values
dynamically with local or global variables.
Also, you can make the response from the backend system visible in the app by writing the
response as message in a text field.
Insert six Text widgets on the Canvas and rename the last one to MessageBox.
OData 306
var totalnumberofseats =
ConvertUtils.integerToString(ret.value[0].Totalnumberofseats);
var currency = ret.value[0].Currency;
info = "Your flight price was " + flightprice + " " + currency +
". " + "There are " + numberofoccupiedseats +
" occupied from " + totalnumberofseats + " seats in total.";
}
Text_5.applyText("" + info);
Run the application and book a flight and cancel a flight to see the error messages.
To create a meaningful application in the sense of an intelligent application, the best would be to
display the backend data via a live connection to a BEx Query. Like this you would be able to see
the changes (the booked and canceled seats) in the data directly after clicking the buttons and
executing the actions.
Therefore, in analytics designer you have programmatic access to these data, which can be used
for any purpose beyond the visualization in a table or a chart. For example, you can read and
display one member in a text widget.
You can focus on the following capabilities regarding access to OData entity sets:
ODataError: {
code: string;
message: string;
target: string;
details: ODataError[];
}
OData 308
Example:
ODataService_1.getEntitiesFromEntitySet("Equipments");
• orderby
• select
• skip
• top
Example:
In the following example a list of Entities is returned, filtered by the specified query options:
ODataService_1.getEntitiesFromEntitySet("Departments", {filter: "contains(Sector,
'Consulting')", orderby: "Sector asc"});
• orderby
• select
• skip
• top
Example:
In the following example, the number of entities in the entity set "TEAMS" is returned:
ODataService_2.getEntitiesCountFromEntitySet("TEAMS");
In the following example, the number of entities in the entity set "TEAMS" is returned, where the
first five entities are skipped and the next ten entries are returned:
ODataService_2.getEntitiesCountFromEntitySet("EMPLOYEES", {skip: 5, top: 10});
OData 309
Using the Post Message script API, you as the application developer can realize either of the
following scenarios:
Then you can trigger bi-directional communication between the host HTML page and analytic
application using the provided functions.
10.1.1 postMessage
This is to post messages from the analytic application to the host HTML page.
When an end user triggers a callback function on the side of the analytic application, the callback
function sends out data to notify the parent receiver page which hosts the iFrame, or, when there
are multiple levels of web pages embedded in one another, to the top-level HTML page of a
specific target origin.
You define whether to send data to a parent or the top HTML page by means of the parameter of
the PostMessageReceiver.
Post Message API 311
10.1.2 onPostMessageReceived
This is to handle messages sent from the host parent or top HTML page in the analytic application.
In scenario 2 depicted below, the event can also handle messages sent from an HTML page
embedded via the web page widget in an analytic application.
Note: We advise you always to check the origin when receiving an event-triggered message,
because a malicious site can change the location of the window and therefore intercept the data
you sent using the postMessage event without your knowledge.
In the current scenario, the parent window which hosts the iFrame can post messages to the
analytic application's iFrame window of specific target origin. The messages posted are then
retrieved by the analytic application and trigger changes accordingly, such as updating some input
data.
10.1.3 Example
You can embed an analytic application in a host HTML page. The URL of the host HTML page is
http://localhost:8080.
First, you want to allow end users to post the company selection in the analytic application to the
host HTML page. Write the script below for the sending button:
var message = RadioButtonGroup_Company.getSelectedText();
Application.postMessage(PostMessageReceiver.Parent, message,
"http://localhost:8080");
Then you want to allow end users to display the message received from the Host HTML page in
a text box of the embedded analytic application.
if (origin == "http://localhost:8080") {
Text_ReceivedMessage.applyText(message);
}
The event for handling messages sent from embedded application is as follows: Once the
messages is received, the host application can use the onPostMessageReceived() event to handle
the messages.
Scheduling a Publication 314
11 Scheduling a Publication
To share an analytic application with other users as well as external users via e-mails at a specific
start time and with a specific recurrence, you can schedule an analytic application for publication.
Recipients will receive an e-mail with a link to the analytic application (if so configured) and an
exported PDF file as an attachment.
Once a publication is scheduled, the related task will be available in Calendar. The Design panel
of this task will have the same configurations as those in Schedule Publication dialog. The
configurations are editable if the task isn't complete, and read-only if it's done.
In the example screenshot below, after clicking in the Schedule Publication panel, a dialog of
message details pops up. The first row in the table is the system message generated by this
scheduling task. The second row is a user-defined message, which adds additional information
related to this scheduling task.
Scheduling a Publication 318
You, as an analytic application developer, can manually define the message using the following
script API:
Scheduling.logMessage(messageType: SchedulingMessageType, messageText: string)
enum SchedulingMessageType {
Error,
Info,
Warning
}
Data Change Insights 319
In addition, you, as an analytic application developer, can use the Data Change Insights script
API to build a Data Change Insights-related application according to your needs.
To start using the Data Change Insights script API, add a Data Change Insights component by
navigating to Outline > Scripting > Data Change Insights, then clicking the + (plus sign).
Example:
The following sample code retrieves an array of snapshot dates, saves that array in script variable
SV_Snapshots, then populates a checkbox group with the dates:
var SV_Snapshots = DataChangeInsights_1.listRecentSnapshotDates();
for (var i = 0; i < SV_Snapshots.length; i++) {
Data Change Insights 320
CheckboxGroup_1.addItem(i.toString(), SV_Snapshots[i].toLocaleDateString());
}
The result below shows that there are two snapshots: one from February 1, 2021 and one from
February 2, 2021.
The following sample code compares the current application state with the first selected snapshot
of the checkbox group and displays the comparison result in the text area:
var selection = CheckboxGroup_1.getSelectedKeys();
if (selection.length > 0) {
var snapshotIndex = ConvertUtils.stringToInteger(selection[0]);
var result = DataChangeInsights_1.compareApplicationStateWithSnapshot(
SV_Snapshots[snapshotIndex]);
if (result.status === DataChangeInsightsStatus.Ok) {
var insights = result.insights;
if (insights.length > 0) {
var contents = ArrayUtils.create(Type.string);
for (var i = 0; i < insights.length; i++) {
var description = insights[i].content;
description = description.split("<keyword>").join("");
description = description.split("</keyword>").join("");
contents.push(description);
}
TextArea_1.setValue(contents.join("\n\n"));
}
}
}
The result is shown below in the text area: The gross margin per location changed between
February, 1 2021 and today.
Data Change Insights 321
The following sample code compares the first and second selected snapshots of the checkbox
group and displays the comparison result in a text area:
var selection = CheckboxGroup_1.getSelectedKeys();
if (selection.length > 1) {
var sourceIndex = ConvertUtils.stringToInteger(selection[0]);
var targetIndex = ConvertUtils.stringToInteger(selection[1]);
var result = DataChangeInsights_1.compareSnapshots(SV_Snapshots[sourceIndex],
SV_Snapshots[targetIndex]);
if (result.status === DataChangeInsightsStatus.Ok) {
var insights = result.insights;
if (insights.length > 0) {
var contents = ArrayUtils.create(Type.string);
for (var i = 0; i < insights.length; i++) {
var description = insights[i].content;
description = description.split("<keyword>").join("");
description = description.split("</keyword>").join("");
contents.push(description);
}
TextArea_1.setValue(contents.join("\n\n"));
}
}
}
The result is shown below in the text area: The gross margin per location changed between
February 1, 2021 and February 2, 2021.
Data Change Insights 322
To configure the data repository where snapshots are stored, you need to have the Remote
Repository Snapshot privilege create.
Data Change Insights 323
In the submenu you can select a subscription level from the following values:
• Subscribed: High Importance
• Subscribed
• Unsubscribed
You can define a subscription range with the Data Change Range Settings dialog:
Data Change Insights 324
structure: string,
structureMember: string,
min: number,
max: number,
isInclude: boolean, // optional (default: false)
isDeltaValue: boolean, // optional (default: false)
isMinOrEqual: boolean, // optional (default: false)
isMaxOrEqual: boolean, // optional (default: false)
isAbsoluteValue: boolean // optional (default: false)
}
Example:
In the following examples, several subscription range settings are shown for different data models:
When you add a Data Change Insights component, a Data Change Insights panel opens:
Data Change Insights 326
In this panel you can set the component’s version in the following ways:
• Auto Saved File Version (Default) – The version is specified by the timestamp taken
when the analytic application is saved.
• Manually Defined Version – You can specify the version by manually entering a version
number.
Example:
https://www.example.com/.../app.html?APP_PERFORMANCE_LOGGING=true#...
/DBE810069927EEBD227B89F91B977AD5/?mode=view
In the following screenshot you see an example of the Analytic Application Script Performance
Popup:
A blue bar visualizes the script execution time of the onInitialization event script (ca. 13,000
ms).
Two red bars visualize operations which influence script execution performance in a negative way
– and which can be avoided by changes to the script. In this example they represent (1) waiting
for a specific table to be loaded in the background and (2) fetching the description of a dimension
member.
To get a tooltip displaying the execution time, hover over a blue or red bar.
To hide a bar, click on it. The remaining bars are rescaled to fill the width of the Analytic
Application Script Performance Popup.
The purpose of the analytic application is to set a filter, then to retrieve the description of a member
of Table 25’s data source and to assign this description to a text field.
When you run the analytic application and then open the Analytic Application Script Performance
Popup, the following picture is shown:
Performance Best Practices 329
Its red bars reveal that the onInitialization event script is mostly waiting for loading Table 25
in the background (large red bar) and for fetching a description (small red bar).
The Analytic Application Script Performance Popup shows that the script runs much faster than
before. But still, the analytic application spends some time waiting for Table 25 and for fetching
the description.
In the analytics designer, for Table 25, in the Builder panel, navigate to Properties and enable
Pause Data Refresh. Note: This automatically disables Planning Enabled.
Run the analytic application again, then open the Analytic Application Script Performance Popup:
Performance Best Practices 330
In the Popup above there is still a first red bar representing a few milliseconds of waiting time. In
other runs of the analytic application, this bar may disappear altogether. It is present here because
the execution of the onInitialization event script starts and the creation of the initially rendered
widgets hasn’t fully completed.
Now let’s tackle the second red bar, representing the remaining waiting time for fetching the
description.
Run the analytic application again, then open the Analytic Application Script Performance Popup:
The Analytic Application Script Performance Popup shows that the second red bar, representing
the waiting time for fetching the description, is gone.
Performance Best Practices 331
13.2.2.4 Summary
By analyzing the performance of the analytic application with the Analytic Application Script
Performance Popup, the execution time of the onInitialization event script has been
significantly reduced.
13.3.2 Browser
• Use a Google Chrome (or a Chromium-based) browser.
• Take advantage of improved performance with the browser’s caching of analytic
applications. This is particularly important for apps with multiple charts or models. The
cache is valid as long as there are no structural changes made in the analytic
application. Note that this performance improvement is only available for Chrome users
in a Chrome’s non-incognito mode.
13.4.2 Charts
• Reduce the number of charts. This reduces the amount of transferred data.
• Reduce the number of visible data points.
13.4.3 Tables
• Reduce the number of tables. This reduces the amount of transferred data.
• Limit tables to 500 rows and 60 columns.
13.4.4 Images
• Consider limiting the size of images to less than 1MB.
• Prefer image file formats in the order: SVG, PNG, JPG.
13.4.5 GeoMaps
• When using a Bubble Layer, turn on Location Clustering. Enter 1000 for Maximum
Display Points. Both reduces the amount of transferred data.
• When working with thousands of locations, consider using the Choropleth Layer.
Introduction
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.
Now, you, as an analytic application developer, can change this default behavior by activating
loading invisible widgets in the background.
When activated, all widgets that are initially visible are initialized and displayed to the end user.
After that, 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 Builder panel 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.
Getting Started
You can change to the new behavior using the Analytic Application Settings dialog:
You can overwrite this setting with the URL parameter loadInvisibleWidgets, This is useful, for
example, if you, as an analytic application developer, want to decide, which mode is working
better with your analytic application or if you, as an end user, aren’t satisfied with the choice of
the analytic application developer.
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 the visible widgets
are displayed to the end user:
loadInvisibleWidgets=inBackground
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 script code in 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 widgets that are initially invisible.
Especially avoid using the setVariableValue() method if not needed.
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 (including 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.
Performance Best Practices 335
13.5 Scripting
The following best practices apply to scripting in analytic applications.
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.
If it’s sufficient for your scenario you should consider using method getResultSet().
If you need to use method getMembers(), then make sure to use the available options parameter
to limit the list of returned members.
Performance Best Practices 336
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 really need to modify data sources of widgets during the onInitialization event, for
example, by setting different filters or variable values, consider using the Pause Refresh script
API. For more information see section Use the Pause Refresh API.
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>?v01
Model=view:[_SYS_BIC][t.TEST][remotejuice]&v01Par=Manager&v01Val=["SM1","SM2"]
Example:
// BAD
for (var i = 0; i < dimensions.length; i++) {
Table_1.getDataSource().setDimensionFilter(dimensions[i], value);
}
Performance Best Practices 337
// GOOD
var ds = Table_1.getDataSource();
for (var i = 0; i < dimensions.length; i++) {
ds.setDimensionFilter(dimensions[i], value);
}
If you add a table that is based on a planning model, then planning is enabled by default.
Example:
In the following example, an analytic application has a TabStrip widget with two tabs. The second
tab contains a planning-enabled table. Whenever this tab becomes visible, then planning of the
table should be enabled. Whenever this tab becomes invisible, then planning of the table should
be disabled.
You can accomplish this by the following onSelect event script of the TabStrip widget:
var selectedTab = TabStrip_1.getSelectedKey();
if (selectedTab === "Tab_2") {
Table_1.getPlanning().setEnabled(true);
} else {
Table_1.getPlanning().setEnabled(false);
}
Note: When you use planning in combination with Pause Refresh, then Pause Refresh is only
available if planning is disabled. For more information see section Use the Pause Refresh API.
Note: If the filter definition is never visible to the end user of your analytic application, simply use
a dummy description.
Pause the initial refresh of charts or tables when modifying them in the onInitialization
event script
Pause the initial refresh of charts or tables until after you have modified them in the
onInitialization event script, for example, after applying a filter. This improves the analytic
application's startup time.
Example:
In the following example, an analytic application contains a table. In the onInitialization event
script a filter is applied to the table.
1. Enable Pause Refresh for this table in its Builder panel section Properties > Pause Data
Refresh.
2. Add the following code to the onInitialization event script:
Performance Best Practices 339
// ...
var ds1 = Table_1.getDataSource();
ds1.setDimensionFilter("COUNTRY", "Australia);
ds1.setRefreshPaused(false);
Pause the refresh of invisible charts or tables until they become visible
Pause the refresh of invisible charts or tables until they become visible. This delays the loading
of the data of the invisible charts or tables up to the point when they become visible.
In the following example, an analytic application contains a TabStrip widget with two tabs. The
second tab contains a table. Whenever this tab becomes visible, then disable Pause Refresh of
this table. Whenever the tab becomes invisible, then enable Pause Refresh of this table.
1. Enable Pause Refresh for this table in its Builder panel section Properties > Pause Data
Refresh.
2. Add the following code to the onSelect event script of the TabStrip widget:
var selectedTab = TabStrip_1.getSelectedKey();
if (selectedTab === "Tab_2") {
Table_1.getDataSource().setRefreshPaused(false);
} else {
Table_1.getDataSource().setRefreshPaused(true);
}
In the following example, an analytic application contains a TabStrip widget with two tabs. The
second tab contains two tables. Whenever this tab becomes visible, then disable Pause Refresh
of both tables. Whenever the tab becomes invisible, then enable Pause Refresh of both tables.
1. Enable Pause Refresh for both tables in their Builder panel section Properties > Pause
Data Refresh.
2. Add the following code to the onSelect event script of the TabStrip widget:
var selectedTab = TabStrip_1.getSelectedKey();
var ds1 = Table_1.getDataSource();
var ds2 = Table_2.getDataSource();
if (selectedTab === "Tab_2") {
Application.setRefreshPaused([ds1, ds2], false);
} else {
Application.setRefreshPaused([ds1, ds2], true);
}
Pause the refresh of a chart or table, modify it, then update the chart or table
If you, as an end user, need to apply interactively several script operations to modify a chart or
table but don’t want to wait for the data to be refreshed after every operation, then enable Pause
Refresh to disable the data refreshes to the chart or table, apply your interactive operations, then
disable Pause refresh. The chart or table refreshes its data, according to your modifications.
Performance Best Practices 340
Example:
In the following example, an analytic application contains a table, three filter buttons, and a switch.
When the switch is set to "on", then Pause Refresh is enabled. When the switch is set to "off",
then Pause Refresh is disabled (like in the screenshot below).
This allows the end user to apply a filter by clicking a button (in a more elaborate example: any
combination of filters, for example), redecide, and apply another filter without waiting for the data
to be fetched from the backend. Note that during this phase the data displayed in the table doesn’t
change. When the end user eventually turns the switch to "off", only then the data is fetched
according to the current filter setting and displayed in the table.
You can accomplish this by the following onChange event script of the Switch widget:
if (Switch_1.isOn()) {
Table_1.getDataSource().setRefreshPaused(true);
} else {
Table_1.getDataSource().setRefreshPaused(false);
}
Here are some general rules for using Pause Refresh with planning-enabled tables:
• Enable Pause Refresh with planning-enabled tables when they become invisible at
runtime. This avoids the refresh of this table after any user input to another table that
shares the same planning model.
• Disable the planning of a table before enabling Pause Refresh of the table's data source.
• Disable Pause Refresh of a table's data source before enabling the planning of the table.
Example:
You have an analytic application with several planning-enabled tables. You want to leverage the
rules above to improve the performance of your analytic application.
1. Disable planning for the tables in their Builder panel settings.
2. Enable Pause Refresh for the tables in their Builder panel section Properties > Pause Data
Refresh.
3. Add the following script functions to your application:
function activateTables(tables: Table[], activatePlanning: boolean[]): void
Performance Best Practices 341
4. When one or more tables become visible at runtime (for example, by switching a tab), then
call the function activateTables() and pass the relevant tables with the activatePlanning
flags set to false.
5. When one or more tables become visible at runtime (for example, by switching a tab) and
you want to start planning with them, then call the function activateTables() and pass
the relevant tables with the activatePlanning flags set to true.
6. When one or more tables become invisible at runtime (for example, by switching a tab),
then call the function deactivateTables() and pass the relevant tables. If they are planning-
enabled, then their planning will be automatically disabled.
More Tips
• Disable Pause Refresh before saving bookmarks.
The End and the Future 342
15 Important Links
Open the SAP Help page to find many more information about SAP Analytics Cloud, analytics
designer:
https://help.sap.com/viewer/product/SAP_ANALYTICS_CLOUD/release/en-US
No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of SAP SE or an SAP affiliate company.
The information contained herein may be changed without prior notice. Some software products marketed by SAP SE and its distr ibutors contain proprietary
software components of other software vendors. National product specifications may vary.
These materials are provided by SAP SE or an SAP affiliate company for informational purposes only, without representation or warranty of any kind, and SAP or
its affiliated companies shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP or SAP affiliate company products and
services are those that are set forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as
constituting an additional warranty.
In particular, SAP SE or its affiliated companies have no obligation to pursue any course of business outlined in this docume nt or any related presentation, or to
develop or release any functionality mentioned therein. This document, or any related presentation, and SAP SE’s or its affiliated companies’ strategy and possible
future developments, products, and/or platform directions and functionality are all subject to change and may be changed by S AP SE or its affiliated companies
at any time for any reason without notice. The information in this document is not a commitment, promise, or legal obligation to deliver any mate rial, code, or
functionality. All forward-looking statements are subject to various risks and uncertainties that could cause actual results to differ materially from expectations.
Readers are cautioned not to place undue reliance on these forward-looking statements, and they should not be relied upon in making purchasing decisions.
SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP SE (or an SAP
affiliate company) in Germany and other countries. All other product and service names mentioned are the trademarks of their respective companies. See
www.sap.com/copyright for additional trademark information and notices.