# SAP SAC Widget Add-On Development Guide Widget Add-Ons extend built-in SAC widgets without building from scratch. **Available Since**: QRC Q4 2023 **Source**: [Announcing Widget Add-On](https://community.sap.com/t5/technology-blog-posts-by-sap/announcing-the-new-sap-analytics-cloud-feature-widget-add-on/ba-p/13575788) --- ## Table of Contents 1. [Overview](#overview) 2. [Widget Add-On vs Custom Widget](#widget-add-on-vs-custom-widget) 3. [Supported Chart Types](#supported-chart-types) 4. [JSON Structure](#json-structure) 5. [Implementation Examples](#implementation-examples) 6. [Using Widget Add-Ons](#using-widget-add-ons) --- ## Overview Widget Add-Ons allow customization of SAC's built-in widgets: - Add visual elements to charts - Modify tooltip contents - Override existing styling - Extend plot area rendering **Key Benefit**: Leverage SAC's native visualizations with custom enhancements without building widgets from scratch. --- ## Widget Add-On vs Custom Widget | Aspect | Custom Widget | Widget Add-On | |--------|---------------|---------------| | Purpose | Create entirely new widgets | Extend built-in widgets | | Web Components | Creates new widget | Adds/replaces parts of existing | | Component Types | main, styling, builder | main, builder only | | Use Case | Custom chart types, input controls | Tooltip customization, plot styling | | Complexity | Higher | Lower | --- ## Supported Chart Types ### Tooltip Customization - All chart types **except** numeric point ### Plot Area (General) - Bar/Column charts - Stacked Bar/Column charts - Stacked Area charts - Line charts ### Plot Area (Numeric Point) - Numeric Point only --- ## JSON Structure ### widget-addon.json ```json { "id": "com.company.mywidgetaddon", "version": "1.0.0", "name": "My Widget Add-On", "description": "Customizes chart tooltips", "vendor": "Company Name", "license": "MIT", "icon": "", "webcomponents": [ { "kind": "main", "tag": "my-addon-main", "url": "[https://host.com/addon-main.js",](https://host.com/addon-main.js",) "integrity": "", "ignoreIntegrity": true }, { "kind": "builder", "tag": "my-addon-builder", "url": "[https://host.com/addon-builder.js",](https://host.com/addon-builder.js",) "integrity": "", "ignoreIntegrity": true } ], "properties": { "customColor": { "type": "string", "default": "#336699" }, "showCustomLabel": { "type": "boolean", "default": true } }, "extension": { "target": "tooltip" } } ``` ### Extension Targets | Target | Description | |--------|-------------| | `tooltip` | Customize tooltip content and styling | | `plotArea` | Add visual elements to plot area | | `numericPoint` | Customize numeric point display | --- ## Implementation Examples ### Tooltip Add-On ```javascript (function() { const template = document.createElement("template"); template.innerHTML = `
`; class TooltipAddon extends HTMLElement { constructor() { super(); this._shadowRoot = this.attachShadow({ mode: "open" }); this._shadowRoot.appendChild(template.content.cloneNode(true)); this._props = { customColor: "#336699", showCustomLabel: true }; } connectedCallback() { this._render(); } onCustomWidgetBeforeUpdate(changedProperties) { this._props = { ...this._props, ...changedProperties }; } onCustomWidgetAfterUpdate(changedProperties) { this._render(); } // Called by SAC with tooltip data setTooltipData(data) { this._tooltipData = data; this._render(); } _render() { if (!this._tooltipData) return; const data = this._tooltipData; const titleEl = this._shadowRoot.getElementById("title"); const valueEl = this._shadowRoot.getElementById("value"); const changeEl = this._shadowRoot.getElementById("change"); titleEl.textContent = data.dimensionLabel || "Value"; valueEl.textContent = this._formatValue(data.measureValue); valueEl.style.color = this._props.customColor; if (data.previousValue && this._props.showCustomLabel) { const change = ((data.measureValue - data.previousValue) / data.previousValue) * 100; changeEl.textContent = `${change >= 0 ? "+" : ""}${change.toFixed(1)}% vs previous`; changeEl.className = `tooltip-change ${change >= 0 ? "positive" : "negative"}`; changeEl.style.display = "block"; } else { changeEl.style.display = "none"; } } _formatValue(value) { if (typeof value !== "number") return value; if (value >= 1000000) return (value / 1000000).toFixed(1) + "M"; if (value >= 1000) return (value / 1000).toFixed(1) + "K"; return value.toLocaleString(); } get customColor() { return this._props.customColor; } set customColor(v) { this._props.customColor = v; } get showCustomLabel() { return this._props.showCustomLabel; } set showCustomLabel(v) { this._props.showCustomLabel = v; } } customElements.define("tooltip-addon", TooltipAddon); })(); ``` ### Plot Area Add-On ```javascript (function() { class PlotAreaAddon extends HTMLElement { constructor() { super(); this._shadowRoot = this.attachShadow({ mode: "open" }); this._props = { showTargetLine: true, targetValue: 0, targetColor: "#ff6b6b" }; } connectedCallback() { this._render(); } onCustomWidgetBeforeUpdate(changedProperties) { this._props = { ...this._props, ...changedProperties }; } onCustomWidgetAfterUpdate(changedProperties) { this._render(); } // Called by SAC with chart context setChartContext(context) { this._chartContext = context; this._render(); } _render() { if (!this._chartContext || !this._props.showTargetLine) { this._shadowRoot.innerHTML = ""; return; } const { width, height, yScale } = this._chartContext; // Defensive check: Validate yScale is a function before using if (!yScale || typeof yScale !== "function") { console.warn("[PlotAreaAddon] Chart context missing valid yScale function"); this._shadowRoot.innerHTML = ""; return; } const y = yScale(this._props.targetValue); this._shadowRoot.innerHTML = ` Target: ${this._props.targetValue} `; } get showTargetLine() { return this._props.showTargetLine; } set showTargetLine(v) { this._props.showTargetLine = v; } get targetValue() { return this._props.targetValue; } set targetValue(v) { this._props.targetValue = v; } get targetColor() { return this._props.targetColor; } set targetColor(v) { this._props.targetColor = v; } } customElements.define("plotarea-addon", PlotAreaAddon); })(); ``` --- ## Using Widget Add-Ons ### Enabling Add-Ons in Stories 1. Open story in Edit mode 2. Select a supported chart widget 3. Open Builder panel 4. Scroll to **Custom Add-Ons** section 5. Toggle **Enable Custom Add-Ons** to ON 6. Select your installed add-on ### Installation Same process as custom widgets: 1. Navigate to **Analytic Applications** > **Custom Widgets** 2. Click **+** to add new 3. Upload the add-on JSON file 4. Widget Add-Ons appear in the add-ons dropdown for supported widgets --- ## Builder Panel for Add-Ons ```javascript (function() { const template = document.createElement("template"); template.innerHTML = `
`; class AddonBuilder extends HTMLElement { constructor() { super(); this._shadowRoot = this.attachShadow({ mode: "open" }); this._shadowRoot.appendChild(template.content.cloneNode(true)); this._shadowRoot.getElementById("colorInput").addEventListener("input", (e) => { this._fire({ customColor: e.target.value }); }); this._shadowRoot.getElementById("labelCheckbox").addEventListener("change", (e) => { this._fire({ showCustomLabel: e.target.checked }); }); } _fire(properties) { this.dispatchEvent(new CustomEvent("propertiesChanged", { detail: { properties } })); } onCustomWidgetBeforeUpdate(changedProperties) {} onCustomWidgetAfterUpdate(changedProperties) { if (changedProperties.customColor !== undefined) { this._shadowRoot.getElementById("colorInput").value = changedProperties.customColor; } if (changedProperties.showCustomLabel !== undefined) { this._shadowRoot.getElementById("labelCheckbox").checked = changedProperties.showCustomLabel; } } } customElements.define("addon-builder", AddonBuilder); })(); ``` --- ## Key Differences from Custom Widgets 1. **No Styling Panel**: Add-ons only support main + builder components 2. **Extension Target**: Must specify what part of the widget to extend 3. **Context Data**: SAC provides chart context (scales, dimensions, data) via methods 4. **Limited Scope**: Can only modify supported parts of supported chart types --- ## Resources - [Widget Add-On Announcement](https://community.sap.com/t5/technology-blog-posts-by-sap/announcing-the-new-sap-analytics-cloud-feature-widget-add-on/ba-p/13575788) - [Widget Add-On Samples](https://community.sap.com/t5/technology-blog-posts-by-sap/sap-analytics-cloud-custom-widget-amp-widget-add-ons-samples-preview/ba-p/13585313) - [SAP Samples Repository](https://github.com/SAP-samples/analytics-cloud-datasphere-community-content/tree/main/SAC_Custom_Widgets) --- **Last Updated**: 2025-11-22