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
      • Official Sisense Discord
    • Use Case Gallery
    Discussions
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    Discussions
    • TagsChevronRightIcon
    White Label OEM
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Programmatically Formatting Bar Chart Widget Value Labels in Sisense

                                                                                               

      Programmatically Formatting Bar Chart Widget Value Labels in Sisense This article outlines ways to programmatically format Sisense Bar Chart Widget Value labels via widget scripts , covering methods to prevent label overlap and apply consistent styling across all labels. Custom Styling for Data Labels The script below enables the formatting of Chart Widget Value labels by setting a custom background color, padding, and border-radius. Ensure the default data label UI option is disabled. Other CSS and Highcharts settings can be added as needed.         widget.on('render', function (se, ev) { ev.widget.queryResult.plotOptions.bar.dataLabels = { backgroundColor: '#f5d142', color: 'white', padding: 5, borderRadius: 5, enabled: true } })       Preventing Label Overlap The script below manually adjusts value label positioning to prevent overlap in densely populated bar chart widgets. The exact formulas for label positioning can be changed as needed.         widget.on('domready', function (se, ev) { var barWidth = $('.highcharts-series-group .highcharts-series rect', element).width(); $('.highcharts-data-labels .highcharts-label', element).each(function () { var labelWidth = $(this).find('rect').width(); var labelHeight = $(this).find('rect').height(); $(this).find('rect').attr('x', ($(this).find('rect').attr('x') + 2)); $(this).find('rect').attr('height', barWidth); $(this).find('rect').attr('y', ((labelHeight - barWidth) / 2)); }) })         Dynamically Increase Space for Labels If bar value labels overlap with the chart bars, you can dynamically adjust the maximum value on the y-axis to create additional space. A different formula, or a hard-coded value, can also be used as the y-axis maximum value.         widget.on('processresult', function (se, ev) { var maxValue = 0; var increasePercent = 0.2; ev.result.series.forEach(function (series) { series.data.forEach(function (dataItem) { if (dataItem.y > maxValue) maxValue = dataItem.y; }) ev.result.yAxis[0].max = maxValue + (increasePercent * maxValue); }) })       Conclusion These scripts enable customizing dynamically formatted and well-positioned data labels in your Sisense charts, enhancing readability and aesthetics beyond the default Sisense data bar data labels in bar chart widgets. For further discussion of these types of scripts, see the  Dynamically Formatted Data Labels article Example Of Custom Labels Added via Scripting   Y-Axis Maximum Set To a Very Large Value Check out this related content:  Academy Documentation

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago • Last reply 2 months ago
      1
               
    • Blog banner
      • How-Tos & FAQsChevronRightIcon

      Adding additional dimensions to the Scatter Map widget tooltip

                                                       

      Adding additional dimensions to the Scatter Map widget tooltip Additional dimensions can be added to the hover tooltip of the Scatter Map widget type, beyond the default limit of three, to include more information from other dimensions about a location in a Scatter Map widget. This can be accomplished by using a combination of the widget's before query event  to add additional dimension data for the hover tooltips and the  before datapoint tooltip event to incorporate these dimensions into the tooltip. This method of modifying the query using the "beforequery" event can also be applied to all other widget types   // Add extra parameters to be used in tooltips by modifying query widget.on('beforequery', function (se, ev) { // Initial number of widget metadata panels excluding filter panel widget.initialPanelSizeExcludingFilterPanel = ev.query.metadata.filter((panel) => { return panel.panel !== "scope" }).length; // Extra dimensions to show in tooltip, should return a single result, include as many as needed, just add to array // Jaql Objects can be copied from other widgets from the prism.activeWidget.metadata.panels via the browser console // Modify JAQL as needed, title of JAQL is used in tooltip and can be modified to any string widget.extraDimensionJAQL = [ { "jaql": { "table": "Category", "column": "Category ID", "dim": "[Category.Category ID]", "datatype": "numeric", "merged": true, "agg": "count", "title": "Unique Category ID" } }, { "jaql": { "table": "Country", "column": "Country ID", "dim": "[Country.Country ID]", "datatype": "numeric", "merged": true, "agg": "count", "title": "Unique Country ID" } }, ] // Add to default widget query the extra dimensions to be used in tooltips ev.query.metadata = ev.query.metadata.concat(widget.extraDimensionJAQL) }); // Add extra dimensions added with beforequery object to ScatterMap tooltip widget.on("beforedatapointtooltip", (event, params) => { // Convert query results to include only the additional dimensions, and formatted for tooltip template var onlyAdditionalDimensions = widget.queryResult.$$rows.map((withoutDefaultDimensionOnlyAdditional) => { // Remove the default dimensions, first part of row result array var withoutDefaultDimensionOnlyAdditional = withoutDefaultDimensionOnlyAdditional.slice(widget.initialPanelSizeExcludingFilterPanel) // Format for tooltip template, include title from JAQL var extraDimensionObj = withoutDefaultDimensionOnlyAdditional.map((extraDimensionValue, index) => { // Use extraDimensionJAQL for label in tooltip return { "text": extraDimensionValue.text, "title": widget.extraDimensionJAQL[index].jaql.title } }) return extraDimensionObj }); // Object to store extra dimensions params.context.marker.extraDimension = {}; // Use matching queryIndex for tooltip of additional dimensions params.context.marker.extraDimension.arr = onlyAdditionalDimensions[params.context.marker.queryIndex]; // Template for tooltip, modify as needed params.template = ` <div class='geo-text'>{{ model.marker.name }}</div> <div class='measure-holder' data-ng-if='model.measuresMetadata.sizeTitle'> <div class='measure-title slf-text-secondary'>{{ model.measuresMetadata.sizeTitle }}:</div> <div class='measure-value'>{{ model.marker.sizeObj.text }}</div> </div> <div class='measure-holder' data-ng-if='model.measuresMetadata.colorTitle'> <div class='measure-title slf-text-secondary'>{{ model.measuresMetadata.colorTitle }}:</div> <div class='measure-value'>{{ model.marker.colorObj.text }}</div> </div> <div class='measure-holder details-measure-holder' data-ng-if='model.measuresMetadata.detailsTitle'> <div class='measure-title slf-text-secondary'>{{ model.measuresMetadata.detailsTitle }}:</div> <div class='measure-value' data-ng-if="!model.marker.detailsObj.arr">{{ model.marker.detailsObj.text }}</div> <div class="details-wait" data-ng-if="model.marker.detailsObj.pendingDetails"></div> <div data-ng-if="model.marker.detailsObj.arr"> <div class="details-value" data-ng-repeat="a in model.marker.detailsObj.arr">{{a.text}}</div> <div class="details-counter" data-ng-if="model.marker.detailsObj.hasMore"> <div class="details-counter">...</div> <div class="details-counter"> {{'smap.ttip.firstres'|translate:(args={count:model.marker.detailsObj.arr.length})}}</div> </div> </div> </div> <div data-ng-if="model.marker.extraDimension.arr"> <div data-ng-if='model.measuresMetadata.colorTitle'> <div class='measure-holder' data-ng-repeat="a in model.marker.extraDimension.arr"> <div class='measure-title slf-text-secondary'>{{a.title}}:</div> <div class="measure-value extra-dimension-value">{{a.text}}</div> </div> </div> </div>`; });    The JAQL can be changed to any valid JAQL object , and the tooltip template can also be further modified.

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 7 months ago
      3
               
      • Sisense AdministrationChevronRightIcon

      Update and add new Highcharts modules for use in Sisense plugins

                                                                                                                       

      Update and add new Highcharts modules for use in Sisense plugins The JavaScript library framework Highcharts is natively included in Sisense and is utilized in many native Sisense widgets as well as in numerous Sisense plugins. Although Sisense typically does not alter the Sisense Highcharts library version with every release, the versions of Highcharts included in Sisense may change when upgrading to a new major version release. Highcharts can load additional chart types and other types of functionality via JS module files that contain code-adding features such as additional chart types, which can be used within plugins along with additional code to create additional widget types. If a plugin utilizes a Highcharts module, you can source the module directly in the "plugin.json" file's source parameter, as shown in this example:   "source": [ "HighchartModule.js", ],   To determine the current Highcharts version being used in your Sisense version, you can use the "Highcharts" command in the web console while viewing any page on your Sisense server. After identifying the current Highcharts version, you can find the corresponding module hosted on a Highcharts code hosting website using the following URL format: https://cdn.jsdelivr.net/npm/highcharts@${Highcharts_Version}/modules/${module_name}.js For example: https://cdn.jsdelivr.net/npm/highcharts@10.3.3/modules/heatmap.js You can save this module and upload it to the plugin folder or replace the older module JS file simply by copying and pasting the code directly. Be sure to update the "plugin.json" file to point to the new module file if the file name has changed or if this is the first time the module is included. Simply sourcing the module file in the "plugin.json" file is sufficient to load the module into Highcharts; no further code is required to load the module.

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 1 year ago
      2
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Advanced Pivot Widget Scripting - Combining Custom JAQL and the Pivot 2.0 API

                                                                                                       

      Advanced Pivot Widget Scripting - Combining Custom JAQL and the Pivot 2.0 API While the Pivot Table Widget Type is a highly customizable and flexible Sisense widget for representing data in tabular form, certain use cases may be best achieved through custom code and scripting . The Pivot 2.0 JavaScript API facilitates the modification of existing pivot table cells, including updating cell values and adding data to cells not present in the initial results. This article reviews a use case where pivot table values are replaced with specific string text, substituting non-descriptive integers (in this case, the integer "1"). Although adding an additional row dimension could accomplish this visualization, it was not desired in this case due to the existing extensive nesting of three-row dimensions and columns. Adding a fourth row would significantly increase blank spaces in the table, reducing information density and readability. The replacement value will be retrieved from the data source via a second JAQL API request , as the dimension data replacement values are not present in the widget's initial raw results. JAQL is the query language used by all native Sisense widgets to fetch data from data sources. While the JAQL request and code to find the correct value for each cell are specific to this data source, table, and use case, the principles and methods—combining custom JAQL requests and using the Pivot 2.0 JavaScript API—are versatile techniques applicable to many other use cases. This particular widget script can serve as a general example. Other use cases might be simpler but will employ the same techniques. Each half of this solution can be used independently as examples, depending on the specific use case: one half being a secondary JAQL query based on the initial query, and the other matching the cell value to specific raw result values, regardless of their origin. To better illustrate the specific use case, which aids in understanding the code and the widget table from which this request originated, this example involves three rows in the pivot widget, one column, and one value. The relationships between the rows were not 1:1. For example, a "Division" contains multiple "Assets," each with multiple dates and descriptions. Each "Date" has multiple "Description" and "Value" pairs, with each "Description" corresponding to only one "Value." Each "Date" would have multiple "Description" and "Value" pairs. Somewhat unusually these "Descriptions" and "Values" are always 1:1 in the datasource for a specific date (and other rows), no"Description" would correspond to more than one "Value" and the "# of unique Values" would always be "1" in the table. In this pivot table, "Description" is a column, not a row. Below is a screenshot showing some of these "Description" and "Value" pairs, in a different table widget. The "Values" can be both non-numeric strings and integers in text form.   Before any code is applied via widget script a column in the pivot table widget in question appears as below, where any value if present would be "1", as there is one unique value present. The script retrieves the corresponding values from the data source via JAQL and replaces the "1" with the appropriate value. In this example, the values are temperatures, not counts of unique values, as shown below.     Each replacement involves finding the matching value for a given description of the current value. There will always be only one matching value for each description (the description being the columns in this table). Each row will contain multiple descriptions, and each row (which is a date) will not contain all descriptions, so some columns will be blank. The dimensions used for this query are very similar to the current widget metadata dimensions, so these will be copied and used as a template. The " beforequery " event is used to run this additional query just before the main JAQL query of the widget.     // Replace "# of Unique Values" with matching cell contents using custom JAQL call // Script runs before JAQL query sent to fetch data for widget widget.on('beforequery', function (_, widgetObject) { // Copy of current widget JAQL for modification for custom JAQL call let customJAQL = [...widgetObject.query.metadata];   The initial setup involves copying the current widget JAQL for modification for the custom JAQL call. Dimensions and panels not relevant to the request are removed and replaced with relevant ones. Any relevant filters, whether custom and defined in the script, or based on existing widget filters or dashboard filters present may also be added to filter the query values returned from the secondary JAQL request. The current metadata panels of a widget can be logged for assistance in finding the relevant dimensions with the console command:   prism.activeWidget.metadata.panels   The full path to finding a relevant dimension name is:   prism.activeWidget.metadata.panels[panelIndex].items[itemIndex].jaql.dim   Below is one example of replacing and modifying the JAQL payload.   // Replace JAQL of existing widget for values needed to find matching string contents to replace "# of Unique Values" cells // Modify as needed for future use to match names of dimensions as needed customJAQL[3] = { "jaql": { "dim": "[Brand.Brand]" } } customJAQL[4] = { "jaql": { "dim": "[Category.Category]" } }   Now that the modified JAQL query payload is ready, the API request can now be made, via the standard JAQL API endpoint.   // Full query object copy let customQuery = { ...widgetObject.query } // Set query metadata to modified metadata customQuery.metadata = customJAQL; // Function to make a custom JAQL Request function jaqlAPI(jaql) { // Use $internalHttp service if exists const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; // Ajax configurations const ajaxConfig = { url: "/api/datasources/" + encodeURIComponent(jaql.datasource.title) + "/jaql", method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false }; // Use $internalHttp service // else use default ajax request const httpPromise = $internalHttp ? $internalHttp(ajaxConfig, true) : $.ajax(ajaxConfig); // Return response return httpPromise.responseJSON; }; // Make custom JAQL call to return replacement string values let response = jaqlAPI(customQuery);   The Sisense internalHTTP library is used to make this request, as   shown in the examples in this article . This approach can help avoid many request header-related issues when making secondary JAQL requests in a widget or dashboard script. Using the existing widget query and adapting it, instead of creating an entirely new query from scratch, can be a useful technique in various other use cases, this is primarily for convenience and is identical to defining the equivalent JAQL payload in full within the script. The relevant values are now returned from the data source via the JAQL response and can be used to replace the values in the pivot cells with the correct values. Next, a function is defined to match the individual cells with the corresponding replacement value.   // Search for matching value from custom JAQL request with string replacement value for "# of Unique Values" cells function findMatchFromResponse(jaqlResponseValues, cellColumns) { // For date comparisons const parseDate = (dateString) => new Date(dateString).toISOString(); // Loop through JAQL response from custom call to find matching values for current cell for (let row of jaqlResponseValues) { // Set to false whenever any column does not match current cell let foundMatch = true; // Check columns ignores column after index 3, column 4 contains string match if previous three match for (let i = 0; i < 4; i++) { // For date columns convert to date ISO strings and compare if (cellColumns[i].name.includes("(Calendar)")) { if (parseDate(row[i].data) !== parseDate(cellColumns[i].member)) { // If all columns do not match go to next row in response foundMatch = false; break; } } else { // If not date string compare values directly if (row[i].data !== cellColumns[i].member) { // If all columns do not match go to next row in response foundMatch = false; break; } } } // If all columns before column index 4 all match, current row is a match and column index 4 contains the corresponding string contents if (foundMatch) { // Return contents to replace "1" in cell, stop search as match found return row[4].data; // Return the value of the fourth item in the matching row } } // Return null if no match is found in custom JAQL response return null; }   This function can now be used to match rows between the primary and secondary JAQL responses, which have different dimensions but share some row dimensions. The second function parameter corresponds with the specific format the Pivot 2.0 API uses to return values about the current column of the cell, with some modifications. To handle data dimensions, the function converts dates to Javascript programmatic date-type objects. This ensures that date formatting differences do not prevent finding an identical match. Other values are compared directly without conversion. This general logic flow can be adapted to find matching rows between any two JAQL requests with overlapping dimensions. Depending on the exact use case, slight modifications may be necessary. Similar code can be used not only for other pivot table use cases but also, for example, to modify tooltips by adding additional dimensions not present in the widget, for widget types that include tooltips, such as bar and column chart widgets. This particular function uses hard-coded column indexes, which can be changed to adaptive variables or different indexes as needed. Using the Pivot 2.0 API, the cell contents can now be replaced with the previously defined findMatchFromResponse function and the secondary JAQL responses. The Pivot 2.0 API provides the current cell's metadata (such as the exact name of the column), and position in the table (including row and column indexes), and allows overriding the existing value with a new value. The rowIndex: ['member'] ensures that only value cells are affected; no header or direct row value cells are impacted, so no additional conditions are needed to check if the current cell is a value cell. The cell metadata array, which contains information about the cell's row position, is modified by adding the cell's column to the same object. This provides a single array containing all the information needed to use the findMatchFromResponse function to find the matching replacement value with the already retrieved new values.   // Use Pivot 2.0 API to replace cell contents using findMatchFromResponse function to find values // Runs when table renders, but can be set in any widget event before widget.transformPivot({ rowIndex: ['member'] }, function (metadata, cell) { // All "# of Unique Values" are aimed to be "1" in current dataset and table if (!isNaN(cell.value)) { // Copy of current cell values, contains cell position in pivot except "Column" panel let metadataRows = [...metadata.rows] if (metadata.columns && metadata.columns.length > 0) { // Add current cell "Column" panel position metadataRows.push({ "name": "", "member": metadata.columns[0].member }) // Matching contents from custom JAQL for current cell position let newCellContent = findMatchFromResponse(response.values, metadataRows); // If match found if (newCellContent != null && newCellContent.length != 0) { // Replace "1" with matching string value from custom JAQL call cell.content = newCellContent; } else { cell.content = " "; } } } })   This type of functionality can be used independently of this particular instance. For example, the code could be adapted to use a constant dictionary included in the script instead of a secondary JAQL request. Cell styling with CSS style rules can also be added simultaneously with the cell content replacement. Here is the full script:   // Replace "# of Unique Values" with matching cell contents using custom JAQL call // Script runs before JAQL query sent to fetch data for widget widget.on('beforequery', function (_, widgetObject) { // Copy of current widget JAQL for modification for custom JAQL call let customJAQL = [...widgetObject.query.metadata]; // Replace JAQL of existing widget for values needed to find matching string contents to replace "# of Unique Values" cells // Modify as needed for future use to match names of dimensions as needed customJAQL[3] = { "jaql": { "dim": "[Brand.Brand]" } } customJAQL[4] = { "jaql": { "dim": "[Category.Category]" } } // Full query object copy let customQuery = { ...widgetObject.query } // Set query metadata to modified metadata customQuery.metadata = customJAQL; // Function to make a custom JAQL Request function jaqlAPI(jaql) { // Use $internalHttp service if exists const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; // Ajax configurations const ajaxConfig = { url: "/api/datasources/" + encodeURIComponent(jaql.datasource.title) + "/jaql", method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false }; // Use $internalHttp service // else use default ajax request const httpPromise = $internalHttp ? $internalHttp(ajaxConfig, true) : $.ajax(ajaxConfig); // Return response return httpPromise.responseJSON; }; // Make custom JAQL call to return replacement string values let response = jaqlAPI(customQuery); // Search for matching value from custom JAQL request with string replacement value for "# of Unique Values" cells function findMatchFromResponse(jaqlResponseValues, cellColumns) { // For date comparisons const parseDate = (dateString) => new Date(dateString).toISOString(); // Loop through JAQL response from custom call to find matching values for current cell for (let row of jaqlResponseValues) { // Set to false whenever any column does not match current cell let foundMatch = true; // Check columns ignores column after index 3, column 4 contains string match if previous three match for (let i = 0; i < 4; i++) { // For date columns convert to date ISO strings and compare if (cellColumns[i].name.includes("(Calendar)")) { if (parseDate(row[i].data) !== parseDate(cellColumns[i].member)) { // If all columns do not match go to next row in response foundMatch = false; break; } } else { // If not date string compare values directly if (row[i].data !== cellColumns[i].member) { // If all columns do not match go to next row in response foundMatch = false; break; } } } // If all columns before column index 4 all match, current row is a match and column index 4 contains the corresponding string contents if (foundMatch) { // Return contents to replace "1" in cell, stop search as match found return row[4].data; // Return the value of the fourth item in the matching row } } // Return null if no match is found in custom JAQL response return null; } // Use Pivot 2.0 API to replace cell contents using findMatchFromResponse function to find values // Runs when table renders, but can be set in any widget event before widget.transformPivot({ rowIndex: ['member'] }, function (metadata, cell) { // All "# of Unique Values" are aimed to be "1" in current dataset and table if (!isNaN(cell.value)) { // Copy of current cell values, contains cell position in pivot except "Column" panel let metadataRows = [...metadata.rows] if (metadata.columns && metadata.columns.length > 0) { // Add current cell "Column" panel position metadataRows.push({ "name": "", "member": metadata.columns[0].member }) // Matching contents from custom JAQL for current cell position let newCellContent = findMatchFromResponse(response.values, metadataRows); // If match found if (newCellContent != null && newCellContent.length != 0) { // Replace "1" with matching string value from custom JAQL call cell.content = newCellContent; } else { cell.content = " "; } } } }) })   When the script is applied to the widget, the values in the pivot table that were previously indistinguishable "1"s (for "# of unique values") are now replaced with the relevant values for each column, matching the corresponding description-value pair. This achieves the desired functionality, and the pivot table now displays the data as intended. This example is likely more complex than the typical use case, but it provides a comprehensive widget script example. Multiple parts of this script can be used in isolation to suit specific needs. The Pivot 2.0 JavaScript API is a powerful tool for creating highly customized data visualizations. By leveraging its capabilities, developers can customize pivot tables, enhancing the utility and readability of the data visualization. Whether for simple or complex use cases, the flexibility offered by the Pivot 2.0 Javascript API enables a wide range of customizations to meet various data visualization requirements. Share your experience in the comments!     

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0
               
    • Blog banner
      • Embedding AnalyticsChevronRightIcon

      Demonstrating ComposeSDK Styling Of Sisense Dashboard Widgets

                                                                                               

      Demonstrating ComposeSDK Styling Of Existing Sisense Dashboard Widgets When developing   ComposeSDK   embedded applications, there are three principal techniques for embedding or converting an existing Sisense widget. Discussing each method, and some of the benefits associated with them: Direct Rendering existing Sisense Dashboard Widgets with the   DashboardWidget   Function: The expedited approach involves rendering an existing Sisense widget directly using the   DashboardWidget   function. While this method ensures swift integration, it does not allow all data options of the widget to be edited within ComposeSDK or defined as variables, one of the features of native ComposeSDK widgets. Nevertheless, customization of other   parameters, including styling , remains feasible based on the props used. Mandatory parameters include the dashboard and widget ID, which allow the rendering of an existing Sisense widget. Widget Recreation using ComposeSDK to Create Native ComposeSDK widgets An alternative method,   detailed in this article , entails recreating the widget as a native ComposeSDK widget by employing an existing Sisense widget as a template for a new not directly linked ComposeSDK widget. Though potentially more time-intensive, this approach yields a fully customizable native ComposeSDK widget. It stands independent of any specific widget or dashboard on the Sisense server, enabling complete independence to changes made to the original dashboard or widget, or complete deletion of the widget used as the model. Loading Widget Metadata with the useGetWidgetModel function Leveraging the  useGetWidgetModel  ComposeSDK function provides a middle option, detailed in this article . It allows automating the return of widget metadata from an existing Sisense widget, facilitating dynamic modifications within ComposeSDK. This method balances somewhat the autonomy of entirely recreating a widget as a native ComposeSDK widget and rendering a widget as a Dashboard Widget. In this article, we will demonstrate and discuss the DashboardWidget rendering feature, a powerful capability within ComposeSDK that allows the embedding and rendering of existing widgets. The focus will be on exploring the large number of styling options provided by this feature. Among the properties of a DashboardWidget component, the styleOptions parameter determines the styling. This parameter accepts a DashboardWidgetStyleOptions object , which includes a large number of parameters documented in detail below and in this documentation page . To demonstrate a practical implementation of using these styling options, the following code example showcases a Compose DashboardWidget utilizing all documented parameters within the DashboardWidgetStyleOptions object.     import { DashboardWidget } from "@sisense/sdk-ui"; export function ComposeSDKChart(props) { let styleOptions = { "backgroundColor": "lightblue", "border": true, "borderColor": "green", "cornerRadius": "Large", "shadow": "Dark", "spaceAround": "Large", "header": { "hidden": false, "titleAlignment": "Center", "backgroundColor": "lightblue", "titleTextColor": "blue" }, "height": 200, "width": 200 }; return (<DashboardWidget widgetOid={'65ab8958857ff900335db870'} dashboardOid={'65ab8948857ff900335db86e'} styleOptions={styleOptions} title={"My Chart"} />) }        The final result in this example is the widget below, showing the effect of this styling in this specific case:   Discussing each style option individually, including the type of parameter accepted by each style setting. backgroundColor: Type: string - Specifically a string containing an  HTML color Description: This option sets the background color of the widget. In the example, the background color is set to "lightblue," a preset standard HTML color code that is equal to #ADD8E6 as a hex color code. border: Type: boolean  Description:  This option determines whether the widget container has a border or not. In the example, the border is enabled with the value set to true , adding a defined boundary to the widget that can be styled. borderColor: Type: string - Specifically a string containing an  HTML color , Description: Specifies the color of the widget container's border.  cornerRadius: Type: "Large" | "Medium" | "Small" - Three specific options set as specific strings Description: Defines the corner radius of the widget container, similar to the CSS property of the same name, allowing for customization of the widget container's shape. header: Type: object- Contains various parameters that control the styling of the header and title. Description: A style object to customize the widget header. It includes options like background color, divider line toggle, divider line color, header visibility toggle, title alignment, and title text color. header.backgroundColor: Type: string - Specifically a string containing an HTML color Description: Sets the background color of the widget header. header.dividerLine: Type: boolean Description: Controls the visibility of the divider line between the widget header and the chart. header.dividerLineColor Type: string - Specifically a string containing an HTML color Description: Specifies the color of the divider line, if visible. header.hidden Type: boolean Description:   Toggles the visibility of the header and title. header.titleAlignment Type: "Left" | "Center" - Two specific options set as specific strings Description: Alignment of the title within the header. The example centers the title using the value "Center. header.titleTextColor Type: string- Specifically a string containing an HTML color Description: Specifies the text color of the header title. height: Type: number - Number in pixels Description: Sets the total height of the widget in pixels. If not explicitly set will use the height of the container. width: Type: number - Number in pixels Description: Sets the total width of the widget in pixels. If not explicitly set will use the width of the container. shadow: Type: "Medium" | "Light" | "Dark" - Three specific options set as specific strings Description: Defines the shadow level of the widget container, similar to the CSS style of the same name.  Effects styling only when spaceAround is defined. spaceAround: Type: "Large" | "Medium" | "Small"  - Three specific options set as specific strings Description: Specifies the space between the widget container edge and the chart.   With these styling options, one can modify the visual styling of a ComposeSDK DashboardWidget, enabling a large amount of style customization for an otherwise unchanged existing dashboard widget selected from an existing dashboard on the Sisense server. While ComposeSDK DashboardWidgets may not offer the extensive modification capabilities available to native ComposeSDK widgets, such as the ability to completely modify all of widget data options, they still provide a large degree of extensive visual customization through the use of styling options. Share your experience in the comments!   

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 2 years ago
      1
               
      • Embedding AnalyticsChevronRightIcon

      Using "useGetWidgetModel" to Embed an Existing widget in ComposeSDK

                                                                                               

      Using "useGetWidgetModel" to Embed an Existing Widget in ComposeSDK When developing ComposeSDK embedded applications, there are three principal techniques for embedding or converting an existing Sisense widget. Discussing each method, and some of the benefits associated with them: Direct Rendering existing Sisense Dashboard Widgets with the DashboardWidget Function: The expedited approach involves rendering an existing Sisense widget directly using the DashboardWidget function. While this method ensures swift integration, it does not allow all data options of the widget to be edited within ComposeSDK or defined as variables, one of the features of native ComposeSDK widgets. Nevertheless, customization of other parameters, including styling , remains feasible based on the props used. Mandatory parameters include the dashboard and widget ID, which allow the rendering of an existing Sisense widget. Widget Recreation using ComposeSDK to Create Native ComposeSDK widgets An alternative method, detailed in this linked article , entails recreating the widget as a native ComposeSDK widget by employing an existing Sisense widget as a template for a new not directly linked ComposeSDK widget. Though potentially more time-intensive, this approach yields a fully customizable native ComposeSDK widget. It stands independent of any specific widget or dashboard on the Sisense server, enabling complete independence to changes made to the original dashboard or widget, or complete deletion of the widget used as the model. Loading Widget Metadata with the useGetWidgetModel function Leveraging the useGetWidgetModel ComposeSDK function provides a middle ground. It allows automating the return of widget metadata from an existing Sisense widget, facilitating dynamic modifications within ComposeSDK. This method balances somewhat the autonomy of entirely recreating a widget as a native ComposeSDK widget and rendering a widget as a Dashboard Widget. Using the format below, the entire ComposeSDK metadata equivalent of an existing Sisense widget can be imported and be used directly to render a direct ComposeSDK equivalent:   import { Chart, useGetWidgetModel } from "@sisense/sdk-ui"; /** * Retrieves and renders a ComposeSDK equivalent of an existing Sisense widget using one of three methods. * Method 1: Rendering an existing Sisense widget directly as a ComposeSDK DashboardWidget. * Method 2: Recreating the widget independently as a native ComposeSDK widget using a Sisense widget template. * Method 3: Programmatically loading ComposeSDK metadata using the useGetWidgetModel function. * * @param {string} dashboardOid - The ID of the Sisense dashboard containing the widget. * @param {string} widgetOid - The OID of the Sisense widget to be converted. * @returns {JSX.Element} - The ComposeSDK Chart component rendering the converted widget. */ const convertSisenseWidgetToComposeSDK = ({ dashboardOid, widgetOid }) => { const { widget, isLoading, isError } = useGetWidgetModel({ dashboardOid, widgetOid, }); if (isLoading) { return <div>Loading...</div>; } if (isError) { return <div>Error</div>; } if (widget) { // Extract ComposeSDK metadata equivalent of the Sisense widget const widgetProps = widget.getChartProps(); // Render the ComposeSDK Chart component with the converted widget metadata return <Chart {...widgetProps} />; } return null; }; // Example usage: const sisenseToComposeSDKWidget = convertSisenseWidgetToComposeSDK({ dashboardOid: 'yourDashboardID', widgetOid: 'yourWidgetOID', });   The variable widgetProps contains the entire widget metadata equivalent of the Sisense widget. This is a standard Javascript object that contains all of the Sisense metadata in the format ComposeSDK expects. These parameters can be changed in the object similar to any other Javascript object. The useGetWidgetModel function, introduced in the latest ComposeSDK version as of January 2024, fetches the entire ComposeSDK metadata equivalent of a Sisense widget. However, certain errors may arise when the metadata returned is used to render a widget, necessitating minor adjustments to the props returned. For instance, the widget dataSource parameter can includes the server name, which is often "localhost," triggering an "ElastiCube not found" error in ComposeSDK. Mitigate this is possible by eliminating "localhost/" from the returned prop object:   widgetProps.dataSet = widgetProps.dataSet.replace("localhost/" , "")    Similar changes can be made to any other parameters where issues may occur. It is important to note that widgets rendered using useGetWidgetModel retain all limitations and variations of ComposeSDK, and unsupported widget types in ComposeSDK are still not renderable. As an example, ComposeSDK widgets require a Value metadata item, while native Sisense widgets can render with only a category panel item. Additionally, if left permanently in the code, the Sisense widget and its corresponding dashboard must persist, as useGetWidgetModel fetches the widget metadata on each call. As a result, any changes made to the widget in native Sisense will be reflected, which can be useful. Similar to the method discussed here , useGetWidgetModel  can facilitate the conversion of a widget into a fully native ComposeSDK model instead of using the web console of native Sisense, the returned props from "useGetWidgetModel" can be console logged and copied directly. Once the necessary metadata is copied, the function can be "useGetWidgetModel", and the widget will be a fully native ComposeSDK widget. A native Sisense widget: The widget is rendered using ComposeSDK and useGetWidgetModel: The widget metadata returned console logged. These can be copied directly from the console, avoiding potentially the continual use of useGetWidgetModel. Share your experience in the comments!   

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0
               
      • Add-ons & Plug-InsChevronRightIcon

      Plugin - HideChartTypeByGroup

                                                                                       

      Plugin - Hide Widget Type in Chart Type Dropdown From User Groups   This plugin hides widget types from the widget selection dropdown menu, for user groups set in the config of the plugin. The widget types hidden are set in the config. Widgets of that type that are pre-existing in dashboards can still be viewed and edited by all users.   Installation To install this plugin, download and unzip the attachment. Then drop the HideChartTypeByGroup folder into your plugins folder (/opt/sisense/storage/plugins). Enable the plugin on the Add-ons tab in the Admin section, wait for the plugin to build, and the plugin will be enabled. Configure To configure this plugin, set the two parameters in the config.6.js file,  chartTypesToHide, and  userGroupToModify. chartTypesToHide is an array of strings of the chart names to hide in the dropdown menu,  userGroupToModify is an array of group OIDs for which users belonging to those groups the charts defined in chartTypesToHide will be hidden in the dropdown menu. Below is an example of a config.6.js file:   export const config = { // Chart types to hide in the dropdown, case sensitive, include full name chartTypesToHide: ["Bar Chart", "BloX"], // Members of these groups will have this plugin modify the dropdown // Group unique OID, can be copied from group API or from prism.user.groupNames userGroupToModify: ["6532f4a99760e6cbf6e18585", "3265f4a99760e3bbe3e18517"] }           Tip  - Include the full name of the Chart type exactly as it appears in the dropdown to avoid potentially hiding Charts that also include the string.   Below is an example of the plugin hiding the Bar Chart option in the widget selection dropdown for a user that is a member of one of the groups set in userGroupToModify.                                       How did the plugin work for you? What other type of plugin are you looking to learn more about? Let me know in the comments!

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 2 years ago
      2
               
    • Blog banner
      • How-Tos & FAQsChevronRightIcon

      Branding/White Labeling with Sisense

               

      What is Branding? Branding or white-labeling is a way to alter the visual part of an application to match your application's visual style. Sisense provides a list of options for rebranding the visual style of GUI, emails, and so on. Sisense provides the following branding options, including: Look and Feel Email strings customization Email logos customization Basic general branding   Look and Feel Look and feel allows users to quickly and easily change things like font, panel color, text color, etc.  For more information, read through Sisense's documentation on  Customizing the Sisense User Interface  ( https://documentation.sisense.com/docs/customizing-the-sisense-user-interface ).   Email Strings Customization Sisense has a list of predefined email templates that are used to send reports on dashboards, builds, and so on. In some cases, the text in those emails can be customized. For example, users have the option to replace Sisense with their custom company name.   Text(strings) used in email templates are translated to multiple languages and are saved locally. With this information, users can then alter the text in translation files to change the text in email reports. Translation files for emails can be found under the following path: Linux : /opt/sisense/storage/translations/{Language}/email-templates.js (Or {Server}/app/explore/files/translations/en-US/email-templates.js using Web File Manager) Windows : C:\Program Files\Sisense\app\translations\{Language}/email-templates.js In most cases, English is used for emails, therefore, the folder we need will be en-US. This file contains all the strings that are used in email reports in the following format:     {Email type}: { {token}: '{String}', {token}: '{String}' }     Where token is the variable that should not be removed from the file, new tokens should not be added as well. String is the actual text that will appear in our email, and is therefore the only thing that can be edited. Here is an example of how to change one of the strings under the test_email type:   Once changes are complete, the  Galaxy service/pod should be restarted (Restart Sisense.Galaxy service in Windows) : This will trigger a test email where we will see the changes in the email body text. Before: After:   Email Logos Customization Logos used in email reports can be changed by replacing the default logo files with custom ones. Default images are stored here: Linux : /opt/sisense/storage/emails/images/ (Or {Server}/app/explore/files/emails/images/ using Web File Manager) Windows : C:\Program Files\Sisense\app\galaxy-service\src\features\emails\templates\images\ Note, that if a custom location for a template is specified, the default images and templates will be ignored.  The images are mapped using a name, therefore in order to replace the image, the new file should have an identical name. Below is an example of how this works. Below, is the same default recovery password email referenced earlier in the article: Let us replace recover-password.png file with a new one and restart the galaxy pod: Below is the result:   Basic General Branding Sisense has a list of branding parameters that can be further adjusted to white label your application. It can be located and changed in 2 ways: Using REST API:   Using Configuration Manager ({Host}/app/configuration/system in Linux or Localhost:3030 in Windows):     From here, we can change the logos, strings, and toggle some parameters on or off. Saving should prompt relevant services to restart automatically.   Pay additional importance to the " Emails Templates Directory " parameter as it will allow users to specify a custom path for email templates. Once specified and saved, emails will not use the default folder. Verify that the new path is correct, otherwise, no email will be sent due to no file being found.  Why would you use a custom email template directory? As in most cases, the default template folder is overwritten during version upgrades, meaning changes will be reverted back to default. Users can avoid this issue by using a Custom Email templates folder since it will remain unchanged during the upgrade process.  Note that some releases might introduce new logos or emails that will be missing from the custom folder. In this case, Sisense recommends either re-doing the folder content or copying over missing files. Below are additional resources from the Sisense Knowledge Base: Personalizing the Analytics User Experience with Sisense Themes ( https://community.sisense.com/t5/sisense-community-blog/personalizing-the-analytics-user-experience-with-sisense-themes/ba-p/153 ) Replace the homepage with an embedded dashboard (https://community.sisense.com/t5/knowledge/replace-the-homepage-with-an-embedded-dashboard/ta-p/894) How to Disable Emails from Sisense ( https://community.sisense.com/t5/knowledge/how-to-disable-emails-from-sisense/ta-p/181 )

      OlehChudiiovych
      OlehChudiiovychPosted 3 years ago • Last reply 2 years ago
      1
               
    • Blog banner
      • APIsChevronRightIcon

      Using the "desc" Description Parameter of a Sisense Dashboard via the REST API

                                                                                       

      Using the "desc" Description Parameter of a Sisense Dashboard via the REST API and the Dashboard Javascript Object The description parameter ("desc") of the Sisense dashboard object is notable for its somewhat unique characteristic – it's not accessible or editable through the Sisense Web GUI. Consequently, this parameter can only be programmatically accessed and modified. Despite this limitation, the "desc" parameter proves valuable for storing dashboard descriptions, facilitating programmatic interactions, or embedding usage. Editing the "desc" Parameter The "desc" parameter, part of the dashboard API and available within the Javascript dashboard object , is primarily edited using the REST API . Unlike other dashboard parameters, there is currently no graphical user interface (GUI) method to edit or view this parameter. To edit the "desc" parameter, utilize standard dashboard APIs such as the PATCH dashboard API . The PATCH API requires a full dashboard object, obtainable through the GET dashboard API . After editing the "desc" parameter, use the modified object in the PATCH API endpoint. The API endpoint for modifying the "desc" parameter with a minimal payload is as follows:   PATCH ${SisenseServerDomain}/api/dashboards/{dashboardID}     with a JSON payload solely of the new description parameter with the format:   { "desc": "Description of Dashboard Here" }       Reading the "desc" Parameter The description parameter can be accessed via the standard GET dashboard API endpoints or through the dashboard JS object . The "desc" parameter is available through the desc property in the JS object. Republishing Changes Republishing Dashboard Changes If a dashboard is shared with other users, republish the dashboard for the changes to be visible to all other users who have access to the dashboard. Using the standard GUI republish button or the Republish (shares) API, following the same procedure as any other dashboard change in other parameters (such as in dashboard widgets). Dashboard republishing uses the standard shares API . An example endpoint for republishing:   POST /api/shares/dashboard/${dashboardID}     The payload of this request is a standard dashboard sharesTo object.      Accessing the "desc" Parameter Using the Javascript Dashboard Object  The path to the `desc` parameter when viewing a dashboard in the web-native UI is available with the JS path:   prism.activeDashboard.desc     prism.activeDashboard  refers to the current active dashboard object on the prism object .       Use Cases of the Description Parameter Although not extensively used within the Sisense UI, the "desc" parameter proves beneficial for web applications hosting Sisense in an embedded form, including Sisense.js and EmbedSDK . These applications can utilize the "desc" parameter for displaying descriptions as required or programmatically interacting with them. In Sisense.js, the dashboard object mirrors the console's dashboard object and can be used for both display and programmatic purposes. In EmbedSDK, a modified dashboard object is accessible through the asynchronous  getCurrent EmbedSDK function   , providing the current dashboard state.   Similar steps apply to modifying other dashboard parameters via the API, and these parameters can also be read from the JS API dashboard object. Share your experience in the comments! 

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0
               
    • Blog banner
      • How-Tos & FAQsChevronRightIcon

      Converting an Existing Sisense Widget to a Dynamic ComposeSDK Widget Component

                                                                                                                       

      Converting an Existing Sisense Widget to a Dynamic ComposeSDK Widget Component Using either the widget Sisense API's , or the Sisense widget JS API object , it is possible to determine all components and parameters of a widget required to create an equivalent dynamic ComposeSDK widget component. This approach allows the extraction of all components and parameters of a widget, enabling the creation of an equivalent dynamic ComposeSDK widget component without directly referencing or relying on a specific widget or dashboard ID. The metadata of an existing widget contains all the information needed to create a dynamic version of an existing Sisense widget. It is also possible to use an existing widget ID and dashboard ID to render an existing widget in ComposeSDK , but this does not take full advantage of the capabilities of ComposeSDK to generate new widgets directly.  This method ensures flexibility, as it doesn't depend on the continued presence of the widget on the Sisense server.  The metadata of an existing widget holds all the necessary information to craft a dynamic version. While it's possible to use an existing widget ID and dashboard ID to render the widget in ComposeSDK, this doesn't fully exploit the capabilities of ComposeSDK for generating new widgets directly. The prism widget object  encompasses the metadata of all components essential for the widget. By utilizing the metadata panels object accessible in the JavaScript developer console, one can extract all relevant data about an existing widget for conversion to a dynamic ComposeSDK widget: Using this general JS widget object path, all metadata panels can be converted in sequence to dynamic ComposeSDK form:   prism.activeWidget.metadata.panels[panelIndex].items[itemIndex].jaql   The initial widget metadata in native Sisense, viewable using the above JS code using the JS widget API, will return all the information needed for conversion to a dynamic ComposeSDK widget. The initial widget to be converted into native Sisense in this article is below:   This above JS code using the JS widget API object will return all the values needed to convert to a dynamic ComposeSDK widget. From the information gathered from the first two panels, one can determine both the category dimension and the value used. The visible aggregation type  such as the sum method, can also be identified. Translating this into standard ComposeSDK syntax would be equivalent to:   dataOptions={{ category: [DM.Commerce.Gender], value: [measures.sum(DM.Commerce.Revenue)], }}   Similarly, the filter state of a dashboard or widget filter can be extracted and converted to dynamic ComposeSDK form. The dimension of the filter and the member state can be extracted to form the filter parameter of the chart. The addition of a filter to the chart, as reflected in the widget metadata panels, can be represented in ComposeSDK as follows: Using the dimension and member values from the panels, the ComposeSDK equivalent would be:   filters={[filters.members(DM.Commerce.Gender, ["Male"])]}   Applying filters to the widget will yield identical results in both ComposeSDK and native Sisense. Before the widget filter is applied (these screenshots are ComposeSDK widgets): and after: The final ComposeSDK dynamic widget code in this case is:   <Chart dataSet={DM.DataSource} title={"GENDER BREAKDOWN"} chartType={"pie"} dataSource={DM.DataSource} dataOptions={{ category: [DM.Commerce.Gender], value: [measures.sum(DM.Commerce.Revenue)], }} filters={[filters.members(DM.Commerce.Gender, ["Male"])]} />   This code can be dynamically modified, and all parameters can be changed to variables or React props, allowing dynamic changes using the full power of ComposeSDK. Additional filters or dimensions can also be added as needed. This fundamental principle of conversion can be applied to any other widget type or widget with significantly more parameters and dimensions. Widget Styling and other ComposeSDK widget parameters can also be added as needed with ComposeSDK.

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0