Skip to main content
Sisense Community logo
  • Community Feedback
  • Chapters
  • Events
  • Forums
    • Help and How To
    • Product Feedback Forum
    • Strategy & Use Cases
  • Blogs
  • KB Docs
    • KB Docs
    • Add-Ons & Plug-Ins
    • APIs
    • Best Practices
    • Blox
    • CDT
    • Cloud Managed Service
    • Data Models
    • Data Sources
    • Embedding Analytics
    • How-Tos & FAQs
    • Onboarding
    • PySisense
    • Security
    • Sisense Administration
    • Sisense Intelligence & AI
    • Troubleshooting
    • Widget & Dashboard Scripts
  • Support
  • Learning
    • Sisense Academy: Free Courses and Certifications
    • Official Developer Documentation
    • Official Product Documentation
    • Official Sisense Youtube Channel
    • Sisense Compose SDK Playground
  • Use Case Gallery
All PostsDiscussionsBlogsIdeasQuestions
Leaderboards
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
Discussions
  • TagsChevronRightIcon
Pivot API
  • Blog banner
    • Widget & Dashboard ScriptsChevronRightIcon

    Dynamic Pivot Table's Column Header Based on Dashboard Filter

                                                             

    Overview This widget script changes a specific column header in a pivot table in response to a dashboard filter selection, making that column more descriptive and context-aware. For example, a "Sales" column header can automatically become "Sales for U.S." when the Country filter is set to United States. The script supports: Using the filter's selected value directly as part of the column header ( append mode) Mapping the filter's selected value to a custom label via a lookup dictionary ( lookup mode) A configurable fallback header for when the filter is disabled, set to Include All, or has multiple values selected A configurable fallback for filter values that have no entry in the lookup dictionary This can be useful for: Making a pivot table's column header reflect exactly what's being measured under the current filter selection, instead of a generic static label Aligning displayed column headers with business-friendly terminology instead of raw filter values Implementation The script reads the current value of a specified dashboard filter and applies a corresponding header to a specific column in a pivot table, on every query. Script /*** Enter your inputs for the nine variables below ***/ // 1. Specify the name of the dashboard filter based on which the column title will be renamed var dashboardFilterName = "Country"; // 2. Specify the panel index where the column to be dynamically renamed is in, i.e. Rows panel = 0, Values panel = 1 var panelIndex = 1; // 3. Specify the index of the column to be dynamically renamed in the specified panel, index starts at 0 var columnIndex = 0; // 4. Specify the static column title for when the dashboard filter is Include All, disabled, or not valid for the script (e.g. multiple values are selected) var fallbackColumnTitle = 'Sales'; // 5. (Optional) Specify the static prefix for the column title var columnTitlePrefix = 'Sales for '; // 6. Choose how the column title is derived from the filter value: // 'append' -> use the filter value directly (e.g. "United States") // 'lookup' -> map the filter value through titleMap (e.g. "United States" -> "US") var titleMode = 'lookup'; // 7. When using titleMode = 'lookup', specify the dictionary of the values var titleMap = { 'United States': 'U.S.', 'United Kingdom': 'Great Britain', 'Canada': 'CA', 'Mexico': 'MEX' // add more mappings as needed }; // 8. Choose how the column title is derived if there's no entry in the titleMap dictionary for the selected filter value: // 'raw' -> use the raw filter value (e.g. "Australia" if the filter is set to Australia and there is no entry for Australia in the titleMap) // 'static' -> use a static placeholder value (e.g. "Other") var titleNotFoundMode = 'static'; // 9. When using titleNotFoundMode = 'static', specify the static column title var titleNotFoundModeColumnTitle = 'Other'; /*** The script below applies the dynamic column titles ***/ widget.on('beforequery', function(w, e) { // find the filter object var filterItem = dashboard.filters.$$items.find(function(item) { return item && item.jaql && item.jaql.title === dashboardFilterName; }); if (!filterItem) return; // determine the filter value to be displayed var filterValue = (!filterItem.disabled && filterItem.jaql.filter.members && filterItem.jaql.filter.members.length === 1) ? filterItem.jaql.filter.members[0] : fallbackColumnTitle; // determine the column title dynamic suffix based on the selected mode var suffix; if (titleMode === 'lookup') { if (titleMap.hasOwnProperty(filterValue)) { suffix = titleMap[filterValue]; } else { suffix = (titleNotFoundMode === 'static') ? titleNotFoundModeColumnTitle : filterValue; } } else { suffix = filterValue; } // determine the final column title var newTitle; if (filterValue === fallbackColumnTitle) { newTitle = filterValue } else { newTitle = columnTitlePrefix + suffix; } // find the filter object's dim var targetDim = widget.metadata.panels[panelIndex].items[columnIndex].jaql.dim; // apply the dynamic column title // 1) Update the persisted metadata /** Uncomment the two lines below if you want to store the updated title permanently into the widget metadata. This is most likely not necessary. **/ // widget.metadata.panels[panelIndex].items[columnIndex].jaql.title = newTitle; //widget.changesMade('widgetscript', ['metadata']); // 2) Update only the matching item in the live outgoing query e.query.metadata.forEach(function(item) { if (item.jaql && item.jaql.dim === targetDim) { item.jaql.title = newTitle; } }); }); Configuration The script behavior is controlled by nine configurable variables at the top of the code, covering which dashboard filter to monitor, which column to target, and how the resulting header text is constructed: dashboardFilterName : Name of the dashboard filter used to determine the column header panelIndex : Index of the widget's panel containing the target column (e.g., Rows panel = 0, Values panel = 1) columnIndex : Index of the column within the specified panel (index starts at 0) fallbackColumnTitle : Static header used when the filter is set to Include All, disabled, or has multiple values selected columnTitlePrefix : Static text prepended to the filter-derived value titleMode : 'append' to use the filter value directly, or 'lookup' to map it through titleMap titleMap : Dictionary mapping filter values to custom header values (used when titleMode is set to 'lookup' ) titleNotFoundMode : 'raw' to fall back to the raw filter value, or 'static' to use a static value, when a filter value has no entry in titleMap titleNotFoundModeColumnTitle : Static header used when titleNotFoundMode is set to 'static' Steps Copy the script above. Open the widget's script editor from the widget menu. Paste the script into the editor. Update the nine configuration variables at the top of the script to match your filter name, panel/column indices, and desired header behavior. Save the script. Refresh the widget and test by changing the dashboard filter's selected value. Limitations Applies to a specific column in a specific pivot table panel. It does not dynamically detect the column. Only applicable to pivot tables. Requires a dashboard filter to be specified by name. If the filter isn't found, no header change is applied. Dynamic column headers are applied to the outgoing query only and are not persisted by default (see the commented-out metadata update lines in the script if persistence is desired). When multiple filter values are selected, the script falls back to fallbackColumnTitle rather than combining values. Lookup-based headers require mappings to be manually maintained in titleMap . Values without a mapping fall back per titleNotFoundMode . Disclaimer This post outlines a potential custom workaround for a specific use case or provides instructions regarding a specific task. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

    Tri Anthony
    Tri AnthonyPosted 3 weeks ago
    0
             
    • Widget & Dashboard ScriptsChevronRightIcon

    Copy a Pivot Cell Value to the Clipboard on Click [Linux]

                             

    Overview The Pivot 2.0 API exposes a cellClick event that fires whenever a user clicks a cell, handing to script the full cell context. This short guide uses that event to add a small convenience: click any cell and its value is copied straight to the clipboard, with a brief toast to confirm. The implementation is intentionally minimal. One event handler plus two small helpers. Availability: The Pivot 2.0 API (including cellClick) is available in Sisense L8.2.1 and later , on Linux versions, and only for Pivot 2.0 widgets.

    Step-by-step guide Open your Pivot 2.0 widget and click Edit Script (the pencil / script icon in the widget menu). Paste the script below into the editor. Click Apply , save the dashboard, and refresh. Clicking any cell now copies its value and shows a confirmation toast. widget.on("cellClick", function (widget, eventData) { const value = eventData?.metadata?.cellData?.content; if (value === undefined || value === null || value === "") { return; } if (typeof value !== "string") { console.error( "[pivot2-copy] Expected cell content to be a string, got " + typeof value + ":", value ); return; } copyToClipboard(value).then(function (ok) { if (ok) { showToast("Copied: " + value); } else { console.error("[pivot2-copy] Failed to copy cell value to clipboard"); } }); }); // --- helpers --------------------------------------------------------------- // Resolves to true only when the text actually reached the clipboard. // Prefer the async Clipboard API (requires HTTPS and, in iframe embeds, // allow="clipboard-write"); fall back to a hidden textarea otherwise. function copyToClipboard(text) { if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text).then( function () { return true; }, function () { return legacyCopy(text); } ); } return Promise.resolve(legacyCopy(text)); } function legacyCopy(text) { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); let ok = false; try { ok = document.execCommand("copy"); } catch (e) { ok = false; } document.body.removeChild(ta); return ok; } function showToast(msg) { const id = "pivot2-copy-toast"; const existing = document.getElementById(id); if (existing) { existing.parentNode.removeChild(existing); } const el = document.createElement("div"); el.id = id; el.textContent = msg; el.style.cssText = [ "position:fixed", "bottom:24px", "left:50%", "transform:translateX(-50%)", "background:#2b2b2b", "color:#fff", "padding:8px 14px", "border-radius:6px", "font:13px/1.3 Arial, sans-serif", "box-shadow:0 2px 8px rgba(0,0,0,.3)", "z-index:99999", "opacity:0", "transition:opacity .15s ease", ].join(";"); document.body.appendChild(el); requestAnimationFrame(function () { el.style.opacity = "1"; }); setTimeout(function () { el.style.opacity = "0"; setTimeout(function () { if (el.parentNode) { el.parentNode.removeChild(el); } }, 200); }, 1400); } How it works Widget.on("cellClick", ...) fires on every cell click and passes eventData. The displayed text lives at eventData.metadata.cellData.content — that's what we copy, so it works for both value cells (the number) and member cells (the label). Empty cells (blank grand-total corners, etc.) are ignored, and a quick type check makes sure cell content is a string, as it should be. Toast is a self-removing element fixed to the bottom of the screen confirms the copy, then fades out after ~1.4s (can be adjusted). Re-clicking replaces any existing toast so they don't stack up. A note on the Clipboard API The copy uses the modern asynchronous Clipboard API (navigator.clipboard.writeText), which is the reliable path, but it has two requirements : Secure context (HTTPS) - on plain HTTP the Clipboard API is unavailable. Embedded dashboards (iframes) need allow="clipboard-write" on the iframe, or the write is blocked by permissions policy. When the Clipboard API is missing or blocked, the script falls back to the legacy approach — a hidden <textarea> plus document.execCommand("copy"). This works in more contexts (including some older embedded browsers) but is deprecated and can fail silently in others. Because both paths return their real success/failure, the toast only shows "Copied:" when the value genuinely reached the clipboard. If both paths fail, nothing is copied and a message is logged to the browser console ([pivot2-copy] Failed to copy...) rather than misleading the user with a false confirmation. Notes and limitations Pivot 2.0 / Linux / L8.2.1+ only, as noted above. The field path metadata.cellData.content reflects the current event payload; if a future version changes the shape, it should be adjusted. This copies the displayed content (formatted text), not the underlying raw numeric value. To copy the raw value target eventData.metadata.cellData.value Resources Pivot 2.0 API — cellClick event and the cell context payload: https://developer.sisense.com/guides/customJs/jsApiRef/widgetClass/pivot2.html#cellclick Widget class: https://developer.sisense.com/guides/customJs/jsApiRef/widgetClass/#widget-class

    Ivan Amoshyi
    Ivan AmoshyiPosted 2 months ago
    0
             
  • Blog banner
    • Widget & Dashboard ScriptsChevronRightIcon

    How to Hide a Column in Pivot 2.0

                     

    This article shows a solution for hiding a column in Pivot 2.0 . There are several ways to hide a column in a Sisense Pivot, and you can find a few of them here in the Sisense Community. This article presents another approach that can have advantages and limitations. Before getting started, it's important to know that Sisense has two different versions of the Pivot widget. This only affects users running Windows or Linux versions earlier than L8.2.1 . The newer version of the Pivot introduces additional capabilities, which also changes the way columns can be hidden. If you're looking for a solution that works with Windows/Pivot 1.0 , try this approach instead. The transformPivot method runs between the data fetching and DOM rendering phases. Instead of directly modifying the CSS or the DOM, it changes the Pivot properties so the widget renders differently. Another important detail is that this method works on individual cells, as shown in the following script: // SETUP: Add this script to a Pivot widget via Widget Script (Edit Widget > Script tab). // It hides specific measure columns from the pivot table without removing them from the data. widget.transformPivot({}, function (metadata, cell) { // SETUP: List the exact column titles you want to hide. // These must match the measure titles shown in the widget header (case-sensitive). // Example: ['Total Revenue', 'Cost', 'Margin %'] const columnsToHide = ["Cost"]; const columnTitle = (metadata.measure && metadata.measure.title) || (metadata.column && metadata.column.title) || null; if (columnTitle && columnsToHide.includes(columnTitle)) { cell.style.width = "0px"; cell.style.minWidth = "0px"; cell.style.maxWidth = "0px"; cell.style.padding = "0px"; cell.style.margin = "0px"; cell.style.border = "none"; cell.style.color = "transparent"; cell.style.fontSize = "0px"; cell.content = ""; } }); The script walks through each cell using transformPivot and retrieves the title of the measure or column. This makes it possible to provide a simple configuration that identifies the specific column title to hide. Since Pivot in general works with individual cells, every cell that belongs to the target column must be hidden. The style properties applied in the script work together to fully hide the column. While some of these properties alone can hide content with CSS, using all of them ensures the entire column is hidden correctly. Why not use display: none ? The Pivot is a smart widget that automatically calculates the layout sizes based on the cells size. When a cell is resized, the Pivot recalculates the layout to keep it responsive. Using display: none removes the element from the layout, which interferes with these calculations. As a result, columns may no longer be positioned correctly, especially when working with nested columns. For this reason, reducing the cell dimensions (such as width) is preferred over removing the element from the layout.

    Example of Pivot using display none to hide
    Using jQuerry You may have seen solutions that manipulate the Pivot using CSS programmatically. While those approaches can work, they are generally not the best practice. // This script hide the first column programmatically widget.on("ready", () => { $(".table-grid__cell--col-0", element).width(0); $(".table-grid__cell--col-0", element).hide(); // Equivalent to display: none }); As explained earlier, the Pivot is a smart widget. Although it is ultimately built with HTML and CSS, it is rendered based on internal properties. Hiding a cell directly in the DOM without updating those properties can lead to unexpected sizing issues because the Pivot may still treat the hidden cell as part of the layout.
    Example of a Pivot using DOM hide option
    Using transformPivot avoids this problem because the changes are applied before the DOM is rendered rather than after. Another potential issue with CSS-based approaches is relying on CSS classes. Class names within the application can change over time, causing the script to break. Whenever possible, avoid depending on CSS class selectors if a more reliable approach is available. References/Related Content  Sisense: Pivot 2.0 API Documentation Previous version of Script Pivot 2.0: Manipulating a Pivot Chart Customizing a Pivot 1.0 Widget (Legacy) Disclaimer: This post outlines a potential custom workaround for a specific use case or provides instructions regarding a specific task. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

    Micael Santana
    Micael SantanaPosted 3 months ago
    0
             
    • Widget & Dashboard ScriptsChevronRightIcon

    How to use a widget.transformPivot function to change the background color of the entire row

                                     

    This article provides an example JavaScript code snippet on how to use a widget.transformPivot function to change the background color of the entire row based on a value within that row.  Please refer to the Sisense developer's documentation to learn more about Pivot 2.0 methods: https://sisense.dev/guides/customJs/jsApiRef/widgetClass/pivot2.html#methods In the example below transformPivot is being executed for a cell context so that we can't access all row cells from it, however, we can use it in order to generate an array of required rows’ indexes in order to change their background color later on a  ready event.   let rowsToChange = [] let changedBuffer = [] let contentToRecolor = '0' widget.transformPivot({}, (metadata, cell) => { if (cell.content === contentToRecolor && !rowsToChange.includes(metadata.rowIndex)) { rowsToChange.push(metadata.rowIndex) } }) widget.on('ready', () => { let widgetMain = $(`widget[widgetid=${widget.oid}]`).length ? $(`widget[widgetid=${widget.oid}]`) : $(`#prism-mainview`) if (changedBuffer.length) { changedBuffer.forEach(i => { widgetMain.find('.table-grid__row-' + i).css({ 'background-color': 'inherit' }) widgetMain.find('.table-grid__row-' + i).children().each(function() { $(this).css({ 'background-color': 'inherit' }) }) }) } rowsToChange.forEach(i => { widgetMain.find('.table-grid__row-' + i).css({ 'background-color': 'pink' }) widgetMain.find('.table-grid__row-' + i).children().each(function() { $(this).css({ 'background-color': 'pink' }) }) }) changedBuffer = rowsToChange rowsToChange = [] })     This script checks if any cell has '0' value and changes all respective rows' color to pink. Feel free to change the contentToRecolor variable to be the value you want to use for changing a background color and let us know if this script works for you. Disclaimer : Please note that this blog post contains one possible custom workaround solution for users with similar use cases. We cannot guarantee that the custom code solution described in this post will work in every scenario or with every Sisense software version. As such, we strongly advise users to test solutions in their environment prior to deploying them to ensure that the solutions proffered function as desired in their environment. For the avoidance of doubt, the content of this blog post is provided to you “as-is” and without warranty of any kind, express, implied, or otherwise, including without limitation any warranty of security and or fitness for a particular purpose. The workaround solution described in this post incorporates custom coding, which is outside the Sisense product development environment and is, therefore, not covered by Sisense warranty and support services.

    Liliia Kislitsyna
    Liliia KislitsynaAdminPosted 3 years ago
    0
             
    • Widget & Dashboard ScriptsChevronRightIcon

    Usage PivotAPI to Beautify Data On Pivot

                     

    Usage PivotAPI to Beautify Data On Pivot In this article, we will review the capabilities of PivotAPI to customize cells. Additionally, we will review important properties, as well as some tricks, we can use to visualize data in a more beautiful way. NOTE: Usage of these technics requires minimal knowledge of JavaScript We will review data formatting for two use cases: Changing styles of the cell depending on the value in the cell; Converting seconds to HH:MM:SS format. Changing styles of the cell depending on the value in the cell Code:     const myTarget = { type: ['value'], values: [ { title: 'formulaTitle' // put here desired column } ] }; widget.transformPivot(myTarget, (metadata, cell) => { //Exclude 0 and empty rows const isPositive = cell.value > 0; const isNegative = cell.value < 0; cell.style = cell.style || {}; let additionalStyle; if (isPositive) { cell.content = `${cell.content} ↑`; additionalStyle = { color: 'green', fontWeight: 'bold' }; } else if (isNegative) { cell.content = `(${cell.content.replace('-', '')}) ↓`; additionalStyle = { color: 'red', fontSize: '18px' }; } if (additionalStyle) { cell.style = Object.assign(cell.style, additionalStyle); } });     Explanation: We need to define columns where the script will be applied. It's done in the variable [myTarget]. This variable has two properties: type - refers to the type of cells, which will be modified by the script; values - an array of the values, which will be modified by the script. So, our script is targeted to update cells, which shows values computed by the formula 'formulaTitle'. Once we have defined the target of the script, we will need to initiate widget.transformPivot(). As the first argument we are passing the target and as the second argument we send a callback function, that will be executed for the cells. The callback function receives information about the cell as the second argument. It contains several important properties: value - this is a numeric value before formatting (for formulas only); content - this is formatted value, which is shown in a frame; style - additional styles of the cell. In terms of our script, we did the following: Check the value in the cell to understand its sign; After this, depending on the sign we update the styles of the cell and change content by adding an arrow up or arrow down. Before After Converting seconds to HH:MM:SS format. Code:     const formulaTitle = 'Formatted'; //Name of the formula, which store integer; const time_separator = ":"; //Delimiter const hourSign = ''; //Symbol for hours if needed const minuteSign = ''; //Symbol for minutes if needed const secondSign = ''; //Symbol for seconds if needed widget.transformPivot( { type: ['value'] //We are going to process values (formulas) only }, processCell ); function processCell(metadata, cell) { if (metadata.measure.title === formulaTitle) { //Find formula with the name defined in a variable [formulaTitle] try { cell.content = computeContent(cell); //Convert value into desired format } catch(err) { console.warn('Unable to process cell'); } }; } function computeContent(cell) { if (!cell.value || isNaN(parseInt(cell.value))) { return cell.content; } else { const value = parseInt(cell.value); const sign = value < 0 ? "-" : ""; const hours = parseInt(value / 3600); const minutes = parseInt((value - (hours * 3600)) / 60); const seconds = value - (hours * 3600) - (minutes * 60); const hoursText = `${hours < 10 ? "0" + hours : hours}${hourSign}`; const minutesText = `${minutes < 10 ? "0" + minutes : minutes}${minuteSign}`; const secondsText = `${seconds < 10 ? "0" + seconds : seconds}${secondSign}`; return `${sign}${hoursText}${time_separator}${minutesText}${time_separator}${secondsText}`; } }     Explanation: This script processes all the cells, which are produced by the formulas. If you have multiple formulas in your pivot, then the time of execution can be quite long. If you want to process some particular cells, then check the first example - it shows how to limit columns, which are processed by the script. We are going to process only values defined by the formula with the title 'Formatted', so we add this condition to the function callback. If this condition is not met (another formula computed the value), we will not execute the further logic. The further logic converts an integer from the cell to the format HH:MM:SS . The computed value is returned and set as [ cell.content ]. Result of execution (original value is stored in the column [Original], the formatted one in the column [Formatted]):   I hope you find this article useful and leverage the knowledge shared about PivotAPI capabilities and their usage. Please share your experience in the comments!    

    Oleksii Demianyk
    Oleksii DemianykAdminPosted 3 years ago
    0