Plugin - Enable Multicolor Legend Items for Conditional and Range
Overview Legend items come with colored boxes that represent the data points in the chart. The color of each box matches the bar/column/bubble in the chart, as long as the measure is set to a single color, and not conditional or range. When conditional or range is used, the legend item's box becomes grey (or the palette's default color for Scatter Charts), because the box cannot natively accommodate multiple colors. This plugin solves this limitation by: splitting the box into smaller strips to accommodate multiple colors, for conditional, or creating a gradient of two colors in the box, for range. Limitations This plugin is only relevant to Bar Charts, Column Charts, and Scatter Charts. Other chart types either do not support conditional/range or have their own version of legends (e.g., Area Map). If there are too many legend items, wider boxes might affect spacing and cause certain items to be cut off. See the Configuration section below for more details. Installation Instructions Download and unzip the plugin zip file. Extract the files directly (do not extract into a new folder); the plugin files are already organized within a folder in the zip file. Upload the multiColorLegendItems folder to the plugins directory on your Sisense server (/opt/sisense/storage/plugins). Check the Add-ons page in the Admin tab (under Server & Hardware) and make sure the new plugin is listed and enabled. Modify the config file as needed (this can be done before of after the upload). Configurations This plugin comes with a configuration file that can be updated as needed. The four configuration settings in the file are: numberOfColorsForWiderBoxes . If there are too many conditions, the box may be too small to accommodate all colors, so the width may need to be increased. In this case, specify the threshold for the number of colors. For example, a threshold of 5 means the box width will be increased if there are five or more colors assigned to it. Otherwise, the original width will be kept. Note: Wider boxes might affect spacing and cause certain legend items to be cut off if there are too many of them. To work around this, the verticalLayout setting can be set to true. The default value is 5. widthMultiplierForConditional . For wider boxes, specify the multiplier. For example, a multiplier of 1.5 means the new width will be 1.5 times the original width for boxes that have more colors than the numberOfColorsForWiderBoxes threshold. The default value is 1.5. widthMultiplierForGradient . If wider boxes are needed for range as well, specify the multiplier. Otherwise, leave the default value of 1. The original width is generally sufficient for range. verticalLayout . Specify whether legend items should be laid out vertically. This is useful when wider boxes are needed and there are too many legend items to fit correctly horizontally. The default value is false. Examples 1. Conditional with 4 colors (less than the numberOfColorsForWiderBoxes threshold) [ALT Text: A comparison of two column charts labeled "Before" and "After," displaying sales and costs data by sales associates. The "Before" chart shows the "Sales" legend item box in grey despite conditional formatting being applied, while the "After" chart correctly displays the four conditional colors.] 2. Conditional with 11 colors (greater than the numberOfColorsForWiderBoxes threshold, the legend item box width is increased) [ALT Text: A comparison of two scatter charts labeled "Before" and "After," displaying sales and costs data by sales associates. The "Before" chart shows the "Sales" legend item box in blue (the palette's default color) despite conditional formatting being applied, while the "After" chart correctly displays all eleven conditional colors, with a wider box to fit them.] 3. Range Auto (both the min and max colors are auto-selected) [ALT Text: A comparison of bar scatter charts labeled "Before" and "After," displaying sales and costs data by sales associates. The "Before" chart shows the "Sales" legend item box in grey despite automatic range (gradient) being applied, while the "After" chart correctly displays a gradient between the automatically selected minimum and maximum colors.] 4. Range Manual with Min Max range type (both the min and max colors are manually selected) [ALT Text: A comparison of bar scatter charts labeled "Before" and "After," displaying sales and costs data by sales associates. The "Before" chart shows the "Sales" legend item box in grey despite manual range (gradient) with Min/Max mode being applied, while the "After" chart correctly displays a gradient between the user-selected minimum and maximum colors.] 5. Range Manual with Min range type (only the min color is manually selected) [ALT Text: A comparison of bar scatter charts labeled "Before" and "After," displaying sales and costs data by sales associates. The "Before" chart shows the "Sales" legend item box in grey despite manual range (gradient) with Min mode being applied, while the "After" chart correctly displays a gradient between the user-selected minimum color and the automatically selected maximum color.] 6. Range Manual with Max range type (only the max color is manually selected) [ALT Text: A comparison of bar scatter charts labeled "Before" and "After," displaying sales and costs data by sales associates. The "Before" chart shows the "Sales" legend item box in grey despite manual range (gradient) with Max mode being applied, while the "After" chart correctly displays a gradient between the automatically selected minimum color and the user-selected maximum color.]318Views2likes0CommentsCustomizing the Sisense User Interface with Interactive Buttons and Icons
Sisense plugins and scripts enable extensive customization of the Sisense user interface, allowing developers to add interactive elements such as buttons and icons to enhance functionality and user experience. A common use case of plugins involves adding clickable icons or buttons that trigger specific plugin features or open custom UI elements. This article outlines the process for adding these interactive elements using a practical example.124Views0likes0CommentsLoading Amchart5 and Other External Libraries via Script Tags in Plugins
This article explains how to load external libraries, such as Amchart5, into Sisense plugins by dynamically adding script tags to the page header to load the library. This method can avoid potential issues associated with other loading techniques but also offers flexibility such as using an external CDN to reduce plugin size and file count. Previous articles have discussed how to load external libraries and modules for Sisense plugins via adding the file to the plugin folder, and adding the file to the "source" parameter array in the plugin.json.476Views1like1CommentCustom Map Widget
Download: Custom Map Widget Note: (Sept 18, 2017) Added support for filtering on shapes. Introduction This article explains how to embed custom map visualizations in Sisense. Purpose/Benefits Sisense has some native mapping functionality but does not allow for importing custom maps. This plugin allows you to view data in whatever map you like. See the references for more information on GeoJSON, Leaflet, and Mapbox. Custom shapes (Counties) overlayed on a base map: Custom shapes (Chinese Provinces) with no base map: Steps The following steps will walk through the process of adding the new chart type and creating a sample map. STEP 1 - ADD THE PLUGIN Download the customMapWidget.zip attachment and unzip the contents into your C:\Program Files\Sisense\PrismWeb\plugins\ folder. If you are using version 7.2 and higher unzip the contents into your C:\Program Files\Sisense\app\plugins\ folder. If the plugins folder doesn't exist, just create it. After those files have been unzipped there, you may also have to restart the web server. Now, when you create a new widget the Custom Map widget should show up in the list of options STEP 2 - PREPARE GEOJSON FOR YOUR CUSTOM MAP This plugin uses GeoJSON files to overlay data as shapes on a map. More information on GeoJSON can be found here, but the general concept is that each file defines a JavaScript object that has a type of FeatureCollection and a list of features. There are different types of features, but the most common type is a polygon. Each feature represents a shape to display on the map and contains properties and geometry. Properties describe the attributes of the entire shape, while geometry is a list of points. Each point is a latitude/longitude combination, and the mapping function displays all points of the shape on the map and then draws a line to connect them. Below is an example of the format for a GeoJSON file. { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "ID": "1", "Name": "MyShape1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [-86.411172, 32.409937], [-86.496774, 32.344437] ] ] } } ] } This plugin comes with 3 GeoJSON files included, but you can add in more if you want to show a different set of shapes. Some government agencies provide geo data for free use, and there are several 3rd party sites that provide free geo data as well. If you want to add in your own geo data, copy the file to customMapWidget/geojson/ folder. STEP 3 - CONFIGURE THE PLUGIN Open the config.js file and update the setting object's properties. urlTemplate - When using mapbox as the tiling server, you will need to provide a tile url mapboxKey - When using mapbox as the tiling server, you will need to provide an api key mapboxId - When using mapbox as the tiling server, you will need to provide a map type to use shapeSeparatorColor - color used for the outlines of the shapes shapeHighlightColor - color used to differentiate the highlighted shape Next update the list of Map options. This is a list of all the map types that are allowed by the widget, so if you are adding a custom GeoJSON file make sure to add it to the list and set the properties accordingly label - Name that appears in the widget editor, for selecting a map type url - Path to the GeoJSON file showBaseMap - should be true or false, based on whether you want to overlay your data on a map keyAttributes - a list of attribute names to use as the key, these get concatenated together using the separator below keySeparator - character to use as a separator for the attribute key prism.customMap = { settings: { mapboxKey: 'myMapboxApiKey', urlTemplate: window.location.protocol + '//api.tiles.mapbox.com/v3/sangil.i6ch70f8/{z}/{x}/{y}.png', mapboxId: 'mapbox.light', shapeSeparatorColor: 'white', shapeHighlightColor: 'black' }, maps: [ { label: 'Canada', url: '/plugins/customMapWidget/geojson/canada-GeoJSON.js', showBaseMap: true, keyAttributes:['NAME'], keySeparator:',', }, { label: 'US Counties', url: '/plugins/customMapWidget/geojson/USCounties-GeoJSON.js', showBaseMap: true, keyAttributes:['STATE','COUNTY'], keySeparator:'', }, { label: 'China Provinces', url: '/plugins/customMapWidget/geojson/china-provinces.js', showBaseMap: false, keyAttributes:['name'], keySeparator:'', }, ], widgets: {} }; STEP 4 - CREATE THE CHART On your dashboard, click the Create Widget button and select Advanced Configuration. Next, select the Custom Map Chart from the chart types menu. Pick a map type from the menu on the right. This selection determines which shape file to load, so you can use this plugin to make several map types available. Next, pick a dimension to use for the Geography dimension. The values from this field need to match the attribute key defined in the map's keyAttributes field. For example, this plugin comes with a GeoJSON file that provides the shape information for all counties in the US. If you open this file, each shape is has properties for STATE and COUNTY that uniquely identify each shape. The included Elasticube combines the STATE and COUNTY codes into a single field named GeoKey. In order to match up data from the Elasticube to the GeoJSON file, the config file specifies that the key is comprised of STATE and COUNTY. The Label dimension (optional) identifies which field to use when displaying the tooltip. The US Counties example uses FIPS codes to identify each county, but you may want to show tooltips with the county names instead. The Value measure is the number to embed into each shape. You can use the standard options to format the number, and the color selections get passed along to each shape. References/Notes Mapbox API - More information about the Mapbox API. This plugin uses mapbox in order to render the maps, so it does require a Mapbox API Key in order to render shape overlays on top of maps. More information on Mapbox API pricing can be found here. LeafletJS - The Mapbox project is an extension of the Open Source LeafletJS JavaScript library. If you're looking to modify the plugin, this can be a useful reference. Natural Earth - This site provides free geo data for download, so if you don't have the geo data already you can likely get it from this site. MyGeoData - This plugin requires a lat/long coordinate system (WGS 84) in order to display overlays on top of maps. If you're custom geo data comes in a different format, this site can likely help convert it to WGS 84 format. Upload your geo files, and convert/export it to a geojson file using the WGS 84 coordinate system. Also, you may need to change the file extension from geojson to js in order for your web server to know how to handle this file type. SampleElasticube.zip3KViews0likes1CommentLimiting Date Filters to Datasource Date Range
Limiting Date Filters to Datasource Date Range By default, Sisense allows users to select any date in a dashboard date dimension filter, regardless of whether data exists for that date in the current dashboard datasource. The native Sisense date filter selection UI highlights the earliest and latest dates available via shading and color indications, but if a user selects a date outside this range, it is accepted as the desired filter from the user. While this behavior is generally the preferred behavior, there are cases where a dashboard creator might prefer the date filter to visually and programmatically reflect only the dates for which data exists in the datasource. In scenarios where the exact number of days in the filter range the data being viewed from is vital, such as when analyzing total date data coverage, performing per-day calculations, or simply ensuring that the visual filter UI accurately represents the actual data period being shown, the date filter should ideally be modified to only display date ranges that have corresponding data. This ensures that the filter not only serves as a control mechanism but also as an accurate visual cue of the available data's temporal range. The dashboard script presented below adds this custom functionality. It works by executing two custom JAQL queries (More documentation on custom script JAQL queries is available here) to fetch the sequentially earliest and latest dates from the datasource. These dates are then used as the bounds to restrict the filter. The dimensions used to fetch the date range and the dimension used to find the matching dashboard filter can be separate or identical dimensions, as they are separated in two variables. The script supports both common date filter formats, traditional "from-to" date ranges and filters that use a list of date members. Additionally, the approach can be extended to accommodate other filter types or JavaScript based actions beyond or not including filter modification when a filter's value falls outside the desired range. For more detail and an addition example see the article Redirect Users to Different Dashboards Based on Dashboard Filters. When a user selects a date that falls outside the available range of the datasource, the code below automatically adjusts the filter. It modifies the selection so that the filter is constrained to the earliest available date if the selected date is too early, or to the latest available date if it exceeds the maximum. If both boundaries are affected, the filter is adjusted on both ends to ensure that only dates with corresponding data are displayed. This solution provides a robust method to visually align your dashboard’s date filter with the actual data available, ensuring that users have a clear and accurate reference of the data period being analyzed. The full code implementing this functionality is provided below. (function () { const enableLogging = true; // Primary date dimension for the filter (change as needed) const dateDim = "[MainTable.Revenue Date (Calendar)]"; // JAQL dimension used to fetch the earliest/latest dates (change as needed) const jaqlDateDim = "[MainTable.Billing Date (Calendar)]"; /** * Simple logging function to enable or disable console logging. */ function log(...msgs) { if (enableLogging) { console.log(...msgs); } } /** * Formats a Date object as "YYYY-MM-DD". */ function formatDate(d) { const yyyy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); return `${yyyy}-${mm}-${dd}`; } /** * Finds the primary date filter (single-level) based on the defined dateDim. * * Note: * - If run within a dashboard script, the variable "dashboard" is already defined. * - If within a plugin, use prism.activeDashboard. * - If within a widget script, use widget.dashboard. */ function findSingleLevelDateFilter() { if (!dashboard.filters || !dashboard.filters.$$items) return null; return dashboard.filters.$$items.find(filterObj => !filterObj.isCascading && filterObj.jaql && filterObj.jaql.dim === dateDim ); } /** * Constructs a JAQL query to fetch a date from the datasource. * @param {string} direction - "asc" to fetch the earliest date, "desc" to fetch the latest. */ function buildDateQuery(direction) { return { datasource: dashboard.datasource, metadata: [ { jaql: { dim: jaqlDateDim, datatype: "datetime", level: "days", sort: direction } } ], count: 1 }; } /** * Executes an asynchronous HTTP request for the provided JAQL query. */ function runHTTP(jaql) { const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; const ajaxConfig = { url: `/api/datasources/${encodeURIComponent(jaql.datasource.title)}/jaql`, method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: true, xhrFields: { withCredentials: true } }; return $internalHttp ? $internalHttp(ajaxConfig, false) : $.ajax(ajaxConfig); } /** * Adjusts the date filter so that its values fall within the datasource range. * For multi-valued filters (using a "members" array), out-of-range dates are removed. * For single-valued filters with "from" and "to" fields, each is updated if outside the available range. * * @param {Object} filterObj - The primary date filter object. * @param {Date} earliestDate - The earliest available date. * @param {Date} latestDate - The latest available date. */ function adjustDateFilterIfOutOfRange(filterObj, earliestDate, latestDate) { if (!filterObj || !filterObj.jaql || !filterObj.jaql.filter) return; const jaqlFilter = filterObj.jaql.filter; let adjustmentMade = false; // Adjust multi-valued filter (members). if (Array.isArray(jaqlFilter.members) && jaqlFilter.members.length > 0) { const originalCount = jaqlFilter.members.length; const validDates = jaqlFilter.members.filter(dateStr => { const d = new Date(dateStr); return !isNaN(d.valueOf()) && (!earliestDate || d >= earliestDate) && (!latestDate || d <= latestDate); }); if (validDates.length < originalCount) { jaqlFilter.members = validDates; adjustmentMade = true; log("Adjusted members filter to valid dates:", validDates); } } // Adjust "from" date if necessary. if (typeof jaqlFilter.from === "string") { const fromDate = new Date(jaqlFilter.from); if (earliestDate && fromDate < earliestDate) { jaqlFilter.from = formatDate(earliestDate); adjustmentMade = true; log("Adjusted 'from' date to:", jaqlFilter.from); } } // Adjust "to" date if necessary. if (typeof jaqlFilter.to === "string") { const toDate = new Date(jaqlFilter.to); if (latestDate && toDate > latestDate) { jaqlFilter.to = formatDate(latestDate); adjustmentMade = true; log("Adjusted 'to' date to:", jaqlFilter.to); } } if (adjustmentMade) { log("Date filter adjusted for dimension:", dateDim); } } /** * Retrieves the earliest and latest dates from the datasource, * then adjusts the primary date filter so that its values fall within that range. */ function updateDateFilter() { const queryEarliest = buildDateQuery("asc"); const queryLatest = buildDateQuery("desc"); Promise.all([runHTTP(queryEarliest), runHTTP(queryLatest)]) .then(([responseEarliest, responseLatest]) => { let earliestDate = null; let latestDate = null; if (responseEarliest && responseEarliest.data && responseEarliest.data.values?.length) { const eStr = responseEarliest.data.values[0][0].data; const dt = new Date(eStr); if (!isNaN(dt.valueOf())) { earliestDate = dt; log("Earliest date from datasource:", formatDate(dt)); } } if (responseLatest && responseLatest.data && responseLatest.data.values?.length) { const lStr = responseLatest.data.values[0][0].data; const dt = new Date(lStr); if (!isNaN(dt.valueOf())) { latestDate = dt; log("Latest date from datasource:", formatDate(dt)); } } const filterObj = findSingleLevelDateFilter(); if (!filterObj) { log("No primary date filter found; cannot adjust date filter."); return; } adjustDateFilterIfOutOfRange(filterObj, earliestDate, latestDate); }) .catch(err => { log("Error fetching datasource date range:", err); }); } // Call updateDateFilter() when filters change. dashboard.on('filterschanged', function () { updateDateFilter(); }); // Call updateDateFilter() on dashboard load dashboard.on('initialized', function () { updateDateFilter(); }); })();298Views1like0CommentsPassing Filters via URL Parameters for Dashboards with Separate Datasources
Sisense includes a native included feature and format for passing URL filters via URL parameters, as documented here. By default, this functionality copies filters in full, including the datasource parameter of the filter, and includes every filter automatically. It results in very long URL's, and includes many parameters that are not always required, as the full filter object is included. Previous Knowledge Base articles articles have discussed how similar behavior can be recreated customized via scripting for more flexible usage. However, those approaches applied only to member-type filters, excluding other filter types and multi-level dependent filters. The code shared below demonstrates a more flexible filter modification via URL modification approach. It includes both creating URL parameters and reading URL parameters for filter modification, whether this code is in a script or plugin. This method applies to all filter types and can be used to transfer filters between dashboards using different datasources. This code works in both dashboard and widget scripts as well as plugins. If your datasources use different dimension names, this code can be adopted to map and match the aligned dimensions.308Views1like0CommentsExploring the Potential of Sisense Jump to Dashboard Filter Configurations
Sisense Jump to Dashboard offers a powerful way to enhance the user experience and streamline data exploration with the help of different filter configurations. By default, all the filters from the parent dashboard, measured values, and widget filters are passed and replaced in the drill dashboard. This guide explains and provides examples of how you can customize the way filters impact the drill dashboard. We'll delve into multiple filter configuration options and provide a step-by-step guide on how to implement them effectively.2.1KViews3likes2CommentsHow to Automatically Hide Accordion Widgets When Clicking on Tabber (Linux)
How to Automatically Hide Accordion Widgets When Clicking Outside the Accordion Window (Linux) Introduction This article addresses the specific scenario where accordion widgets remain visible even after interacting with other interface elements, such as tabber widgets, in a dashboard environment. Step-by-Step Guide Integrate a widget Script in the widget edit mode > click the 3 dots menu > Edit Script: widget.on("ready", function(w, e) { var tabs = document.querySelectorAll(".listDefaultCSS > span.listItemDefaultCSS, .listItemContainer > span.listItemDefaultCSS, .listItemContainer.selected > span.listItemDefaultCSS"); tabs.forEach(function(tab) { tab.addEventListener("click", function(e) { var accordions = document.querySelectorAll(".bk-accordion"); var arrows = document.querySelectorAll(".bk-arrow"); accordions.forEach(function(accrd) { if (accrd.style.opacity == "1") { accrd.style.opacity = "0"; accrd.style.padding = "0"; accrd.style.height = "0"; } }); arrows.forEach(function(arr) { if (arr.style.opacity == "1") { arr.style.opacity = "0"; } }); }); }); }); This script will detect clicks on the tabber widgets and hide any visible accordion windows by setting their opacity, padding, and height to zero. After adding the script, navigate through your dashboard to ensure that accordion widgets hide as expected when clicking on a tabber widget or other areas. [ALT Text: A data visualization dashboard displaying a bar graph titled "Profit per Product." The graph shows profit values for different products with values reaching up to 10,000. The total EU profit is indicated as 23,36K, with a profit rate of 49.81%. Tabs for additional analysis are visible at the top.] Conclusion By incorporating the provided dashboard script, users can enhance their user interface experience by ensuring accordion widgets automatically hide when interacting with other dashboard elements. Despite the current design limitations regarding the accordion's position, the script offers a viable solution to improve dashboard interactions. 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.179Views1like0Comments