Our latest project, the SAC Sunburst Widget, is designed to bridge the gap between the standard visualization capabilities of SAP Analytics Cloud (SAC) and the cover the hierarchical data representation needs of users. With the Sunburst Widget, you can effortlessly visualize and explore multi-level data hierarchies, all by simply downloading the widget and integrating it with your SAC and data model.
But before we dive in, make sure you've read our previous blog post:
1. Transforming SAC with Custom Widgets (Part 1) – link
2. Transforming SAC with Custom Widgets (Part 2 – Gamify) – link
3. Building a Customizable Calculator Widget for SAC – Styling and Builder Panel – link 4. Transforming SAC with Custom Widgets (Part 4 – Custom Widgets Data Binding)– link 5. Gantt Chart Widget with Data Binding for SAP Analytics Cloud– link
6. REST API Integration for SAP Analytics Cloud - Gantt Chart Widget bidirectional example - link
7. Introducing the SAC Cockpit Widget - Version Management, Auditing, Task Management - link
By the way, we encourage you to follow us on [LinkedIn]. :)
Back to our Sunburst Widget
SAP Analytics Cloud is undeniably a robust platform, but there's always room for enhancement. The Sunburst Widget is our answer to the need for an intuitive hierarchical data visualization tool within SAC. This widget allows users to interactively explore data layers, offering a comprehensive view of data distribution across multiple dimensions. The widget is designed to integrate seamlessly with SAC, ensuring a smooth user experience and will work out of the box.
The Code Structure
Before diving into the functionalities, let's familiarize ourselves with the widget's code structure:
Key Components:
Functionality
The Sunburst Widget offers a dynamic, interactive visualization of hierarchical data. With Linked Analysis available the users can click on segments to drill down or up the hierarchy, providing a fluid exploration experience.
Data Binding Definition
Sunburst Widget expects a parent-child hierarchy to function properly. The hierarchy can be created and maintained in the public or private dimension . Later in the Builder panel, the hierarchy must be assigned under Dimension as shown below:
SAC Sunburst Model Definition
In our case, we kept it simple and only have one dimension and one measure for our demo purposes.
The "Global Sales" hierarchy is maintained as follows:
Widget Class Definition
The core of the widget is defined in the `SunburstWidget` class, which extends the `HTMLElement` class. The constructor initializes the widget:
- It attaches a shadow root, allowing for encapsulation of the widget's styles and structure.
- It sets up a resize observer to detect when the widget's size changes.
- It loads the D3.js library, which is essential for rendering the Sunburst chart.
constructor() {
super();
this._shadowRoot = this.attachShadow({mode: 'open'});
this._shadowRoot.appendChild(tmpl.content.cloneNode(true));
this._props = {};
this.resizeObserver = new ResizeObserver(() => this._onResize());
this.resizeObserver.observe(this);
// Load D3.js
const script = document.createElement('script');
script.src = 'https://d3js.org/d3.v7.min.js';
script.onload = () => this._ready = true;
this._shadowRoot.appendChild(script);
}
Lifecycle Methods
- `onCustomWidgetBeforeUpdate`: This method is called before the widget updates. It's used to merge the current properties with the changed properties.
- `onCustomWidgetAfterUpdate`: This method is called after the widget updates. If the data binding changes, it triggers the `_updateData` method to handle the new data.
- `disconnectedCallback`: This method is called when the widget is removed from the DOM. It's used to disconnect the resize observer.
Segment Click Handling
The `_handleSegmentClick` method manages what happens when a segment of the Sunburst chart is clicked. It can set or remove filters based on the segment's selection status:
_handleSegmentClick(d) {
....
if (d.selected) {
linkedAnalysis.removeFilters();
d.selected = false; // Reset the selected flag
} else {
const selection = {};
const key = dimension.key;
const dimensionId = dimension.id;
selection[dimensionId] = d.data[key].id;
linkedAnalysis.setFilters(selection);
d.selected = true; // Set the selected flag
}
}
Resize Handling
The `_onResize` method is triggered when the widget's size changes. It re-renders the chart to fit the new size.
Data Update Handling
The `_updateData` method is responsible for handling new data. It checks the data's state, transforms it if necessary, and then triggers the chart rendering:
_updateData(dataBinding) {
.....
if (this._ready) {
// Check if dataBinding.data is an array
if (Array.isArray(dataBinding.data)) {
// Transform the flat data into a hierarchical structure
const hierarchicalData = this.transformToHierarchy(dataBinding.data);
this.currentData = this.transformToHierarchy(dataBinding.data);
// Render the chart with the hierarchical data
this._renderChart(hierarchicalData);
this._props.metadata = dataBinding.metadata;
} else {
console.error('Data is not an array:', dataBinding.data);
} } }
Rendering the Sunburst Chart
The `_renderChart` method is where the main logic is. It uses D3.js to render the Sunburst chart:
- It sets up the dimensions and the partition layout.
- It defines the color scale based on the top-level parents.
- It creates the SVG container and appends the Sunburst arcs.
- It handles the text labels within the arcs, ensuring they fit and are readable.
_renderChart(data) {
....
const partition = data => {
const root = d3.hierarchy(data)
.sum(d => d.value)
.eachAfter(node => {
if (node.children) {
node.value = d3.sum(node.children, child => child.value);
}
})
.sort((a, b) => b.value - a.value);
return d3.partition()
.size([2 * Math.PI, root.height + 1])(root);
};}
....
Data Binding in the Sunburst Widget
Data binding is a crucial aspect of any data visualization widget. It's the bridge between raw data and the visual representation. In the Sunburst Widget, data binding ensures that the hierarchical data is correctly represented in concentric circles.
Requirements and Prerequisites
For the Sunburst Widget to function correctly, the data must meet certain criteria:
Hierarchical Structure: The data should inherently have a hierarchical structure. This means that there should be parent-child relationships within the data. For instance, in a company's organizational structure, a CEO might be at the top, with managers below, followed by employees.
Unique Identifiers: Each data point should have a unique identifier. This ensures that parent-child relationships can be accurately mapped.
Consistent Data Points: The data should consistently have a dimension and a measure. Dimensions represent the categories or levels of hierarchy, while measures represent the quantitative values associated with each dimension.
Dimensions: These represent the hierarchical levels. For instance, in a dataset representing a company's sales, dimensions could be `Region > Country > City`.
The data might look something like this before being processed:
[
{
"dimensions_0": {
"label": "North America",
"id": "1",
"parentId": null
},
"measures_0": {
"raw": 1000
}
},
...
]
The `transformToHierarchy(data)` Method
This method is the heart of the data binding process for the Sunburst Widget. It takes the flat data structure and transforms it into a hierarchical format suitable for the Sunburst chart.
Here's a step-by-step breakdown:
Initialize the Root: The method starts by creating a root node named "root" with an empty children array.
let hierarchy = { name: "root", children: [] };
Mapping: A map object is initialized to keep track of nodes by their unique identifiers.
let map = {};
Data Iteration: The method then iterates over each data item:
- It checks if the necessary dimensions exist.
- It creates a node with the label, value (from measures), and an empty children array.
- The node is then added to the map using its unique identifier.
- The parent of the node is determined. If the node has a parent (based on the parentId), it's added as a child to its parent node in the hierarchy.
data.forEach(item => { let node = {
name: item.dimensions_0.label,
value: item.measures_0 ? item.measures_0.raw : 0, // Also added a check for measures_0
children: []
}; map[item.dimensions_0.id] = node;
let parent = item.dimensions_0.parentId ? map[item.dimensions_0.parentId] : hierarchy;
parent.children.push(node);
});
Return the Hierarchy: After processing all data items, the method returns the hierarchy.
return hierarchy;
The result is a nested data structure that D3.js can easily use to render the Sunburst chart.
Design and Styling
The Sunburst Widget is a visually appealing representation of hierarchical data. It's not just about the data, though; the styling plays a crucial role in making the chart both informative and aesthetically pleasing. Let's dive into the styling aspects of the Sunburst Widget.
Basic Styling Setup
At the beginning of the code, a template element is created to hold the widget's HTML and styling. Inside this template, the styling is defined within the `<style>` tags.
Styling the Sunburst Arcs
The arcs in the Sunburst chart are styled using the `.sunburst-arc` class. A white stroke separates the arcs, and a transition effect is applied to the fill opacity for smooth visual feedback:
css
.sunburst-arc {
stroke: #fff;
transition: fill-opacity 0.3s ease-out;
}
When you hover over an arc, its fill opacity is reduced to give a visual indication of the selection:
.sunburst-arc:hover {
fill-opacity: 0.7;
cursor: pointer;
}
Text Styling within the Arcs
Text within the arcs is styled to be white, small, and centered:
.sunburst-arc text {
fill: #fff;
font: 10px sans-serif;
text-anchor: middle;
}
Dynamic Styling with D3.js
The Sunburst chart is rendered using D3.js, a powerful library for data visualization. D3.js allows for dynamic styling based on data. For instance, the color of each arc is determined by its parent:
const color = d3.scaleOrdinal()
.domain(topLevelParents)
.range(d3.schemeCategory10);
This code sets up a color scale using D3's built-in color schemes. Each top-level parent in the Sunburst chart gets a unique color from this scheme.
JSON Configuration
The `SunburstWidget.json` file is essentially a configuration file that provides SAP Analytics Cloud (SAC) with all the necessary information about the Sunburst Widget. This ensures that SAC can seamlessly integrate and interact with the widget.
Basic Information
{...
"name": "SunburstWidget",
"description": "Sunburst Diagram Widget",
"newInstancePrefix": "SunburstWidget",
....
"id": "SunburstWidget",
"version": "1.0.1",
"supportsLinkedAnalysisFilterOnSelection": true,
.....
"vendor": "Planifyit",
}
Web Components
The URL where the main JavaScript file of the widget is hosted should be specified here:
"webcomponents": [
{
....
"url": "https://planifyit.github.io/Sunburst/SunburstWidget.js",
...
}
]
Data Bindings
Specifies how the widget binds to data in SAC. The widget expects two types of feeds: dimensions and measures. Dimensions represent categorical data, while measures represent numerical data.
"dataBindings": {
"myDataBinding": {
"feeds": [
{
"id": "dimensions",
"description": "Dimensions",
"type": "dimension"
},
{
"id": "measures",
"description": "Measures",
"type": "mainStructureMember"
}
]
}
}
Integration with SAC
The widgets will be freely accessible on our Github page [Github]. We believe in open-source development and the power of community collaboration.
Just download the JSON file for the SAC Cockpit and upload it in your SAC tenant under Custom Widgets as follows:
Integrating the SAC Sunburst Widget into SAC is a straightforward process. Once you have the `SunburstWidget.json` file ready, you can upload the widget to SAC through the Custom Widgets panel. After uploading, the SAC Widget will be available for use in your dashboards.
Dont forget to follow us on [LinkedIn]. :)
Opmerkingen