Knowledge Base Article

Centering bars and columns when no categories or break by are present in Sisense [linux]

When a Sisense bar or column chart contains only Values and no items in the Break By or Categories panels, Highcharts — the charting library used by Sisense for rendering visualizations still reserves space for grouped series. This can cause the bars or columns to appear offset from their category labels.

When a Sisense bar or column chart contains only Values and no items in the Break By or Categories panels, Highcharts — the charting library used by Sisense for rendering visualizations still reserves space for grouped series; this can cause the bars or columns to appear offset from their category labels.

By using the beforeviewloaded event, we can modify the Highcharts options for a widget before it is rendered, disabling the grouping property so that the bars or columns are centered with their labels. This improves readability and ensures the chart accurately reflects the data layout. (Applicable to Sisense version 2023.11 and later | Cloud and On-Prem deployments.)

 

 

 

 

 

 

Step-by-Step Guide:

  1. Navigate to the dashboard containing the chart you want to adjust.
  2. Create a new bar or column chart widget, or select an existing one that meets the conditions (only Values panel populated, no Break By or Categories).
  3. Open the Widget Script Editor.
  4. Paste the following snippet into the script editor:

    // Purpose:
    // Disables Highcharts series grouping for bar/column charts that have no Categories or Break By,
    // ensuring bars/columns are centered with their labels.
    
    
    function findPanelByTitle(panels, title) {
      var normalizedTitle = String(title || '').toLowerCase();
      return panels.find(function (panel) {
        return panel &&
               typeof panel.title === 'string' &&
               panel.title.toLowerCase() === normalizedTitle;
      });
    }
    
    
    widget.on('beforeviewloaded', function (widget, env) {
      var chartOptions = (env && env.options) ? env.options : {};
      try {
        var panels = (widget.metadata && widget.metadata.panels) || [];
    
    
        var isBarOrColumnChart =
          widget.type === 'chart/bar' || widget.type === 'chart/column';
    
    
        var breakByPanel    = findPanelByTitle(panels, 'break by');
        var categoriesPanel = findPanelByTitle(panels, 'categories');
        var valuesPanel     = findPanelByTitle(panels, 'values');
    
    
        var breakByIsEmpty =
          !breakByPanel || !Array.isArray(breakByPanel.items) || breakByPanel.items.length === 0;
        var categoriesIsEmpty =
          !categoriesPanel || !Array.isArray(categoriesPanel.items) || categoriesPanel.items.length === 0;
        var valuesHasItems =
          !!valuesPanel && Array.isArray(valuesPanel.items) && valuesPanel.items.length > 0;
    
    
        // Apply only if:
        // - The widget is a bar or column chart
        // - Break By panel is empty
        // - Categories panel is empty
        // - Values panel exists and has items
        if (isBarOrColumnChart && breakByIsEmpty && categoriesIsEmpty && valuesHasItems) {
          chartOptions.plotOptions = chartOptions.plotOptions || {};
          chartOptions.plotOptions.series = Object.assign({}, chartOptions.plotOptions.series, {
            grouping: false
          });
        }
      } catch (_) {
        // Do nothing if metadata structure is unexpected
      }
    });


  5. Save and refresh the widget. If the conditions are met, the bars or columns will now be centered with their labels.

Extended Example (Plugin Version):

For a solution that automatically applies to all applicable bar and column charts across all dashboards, the following plugin version can be used. This version attaches the same logic globally via the dashboardloaded and widgetinitialized events.

/**
 * Plugin: BarColumnCentering
 * Purpose: Disables series grouping for bar/column charts with only Values, no Categories or Break By.
 */


function hasEventHandler(model, eventName, handler) {
  var eventHandlers = model.$ngscope
    ? ($$get(prism, '$ngscope.$$listeners.' + eventName) || [])
    : model.$$eventHandlers(eventName);
  return eventHandlers.indexOf(handler) >= 0;
}


function findPanelByTitle(panels, title) {
  var normalizedTitle = String(title || '').toLowerCase();
  return panels.find(function (panel) {
    return panel &&
           typeof panel.title === 'string' &&
           panel.title.toLowerCase() === normalizedTitle;
  });
}


function onBeforeviewloadedCenter(widget, env) {
  var chartOptions = (env && env.options) ? env.options : {};
  try {
    var panels = (widget.metadata && widget.metadata.panels) || [];


    var isBarOrColumnChart =
      widget.type === 'chart/bar' || widget.type === 'chart/column';


    var breakByPanel    = findPanelByTitle(panels, 'break by');
    var categoriesPanel = findPanelByTitle(panels, 'categories');
    var valuesPanel     = findPanelByTitle(panels, 'values');


    var breakByIsEmpty =
      !breakByPanel || !Array.isArray(breakByPanel.items) || breakByPanel.items.length === 0;
    var categoriesIsEmpty =
      !categoriesPanel || !Array.isArray(categoriesPanel.items) || categoriesPanel.items.length === 0;
    var valuesHasItems =
      !!valuesPanel && Array.isArray(valuesPanel.items) && valuesPanel.items.length > 0;


    if (isBarOrColumnChart && breakByIsEmpty && categoriesIsEmpty && valuesHasItems) {
      chartOptions.plotOptions = chartOptions.plotOptions || {};
      chartOptions.plotOptions.series = Object.assign({}, chartOptions.plotOptions.series, {
        grouping: false
      });
    }
  } catch (_) {}
}


prism.on('dashboardloaded', function (e, args) {
  args.dashboard.on('widgetinitialized', function (_, initArgs) {
    var widget = initArgs && initArgs.widget;
    if (!widget || !widget.on) return;


    if (!hasEventHandler(widget, 'beforeviewloaded', onBeforeviewloadedCenter)) {
      widget.on('beforeviewloaded', onBeforeviewloadedCenter);
    }
  });
});

The plugin is available for download below and includes a README file with additional details.

Conclusion

Whether applied to a single widget or deployed globally via a plugin for all widgets, disabling grouping for bar and column charts that have only Values ensures that data is visually aligned with labels. This change reduces confusion and improves chart readability without altering the underlying data.

Disclaimer: This post outlines a potential custom workaround for a specific use case or provides instructions regarding a particular 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.

Published 09-11-2025

1 Comment

  • JeremyFriedel's avatar
    JeremyFriedel
    Sisense Employee

    The full plugin is attached for download as a zip in this comment.

    The full Readme file:

    # BarColumnCentering
    
    ## Description
    
    The **BarColumnCentering** plugin adjusts the rendering of Sisense bar and column charts to ensure that bar or column labels are centered when the chart contains only Values and no items in the Break By or Categories panels. In this situation, Sisense's default grouping leaves empty slots in each category band, causing misalignment between bars/columns and labels.
    
    This plugin automatically detects applicable charts on dashboard load and disables Highcharts series grouping for them, ensuring each bar or column is drawn in the center of its category.
    
    ## Installation
    
    1. **Download and Install the Plugin**
    
       * Extract the `.zip` archive to the Sisense plugins directory:
    
         ```
         /opt/sisense/storage/plugins/
         ```
       * Alternatively, navigate to:
    
         ```
         Admin > System Management > File Management
         ```
    
         and upload the extracted plugin folder to the `plugins` directory.
    
    2. **Activate**
    
       * Refresh the Sisense dashboard to apply the changes.
    
    ## How It Works
    
    When a dashboard loads, the plugin checks each bar or column chart widget before rendering.
    If all of the conditions are met:
    
    * The widget type is either bar or column chart.
    * The Break By panel has no items.
    * The Categories panel has no items.
    * The Values panel exists and contains one or more items.
    
    …the plugin sets the Highcharts `plotOptions.series.grouping` property to `false`. This removes the default per-category grouping behavior, ensuring each bar or column spans the entire category band and aligns with its label.
    
    ## Limitations
    
    * Only applies to bar and column chart widgets where the Break By and Categories panels are empty and the Values panel contains one or more items.
    * Does not affect charts with Break By or Categories panel items present.
    

    The full plugin.json file:

    {
        "name": "BarColumnCentering",
        "source": [
            "main.6.js"
        ],
        "style": [],
        "lastUpdate": "2025-09-15T17:12:54.438Z",
        "pluginInfraVersion": 2,
        "isEnabled": true,
        "version": "1.0.0",
        "folderName": "BarColumnCentering"
    }

    Full main.6.js file:

    /**
     * Plugin: BarColumnCentering
     *
     * Purpose:
     * Adjusts the rendering of Sisense bar and column charts so that bars/columns are visually centered
     * when no Categories or Break By panel items are present, and only Values are defined.
     * This is achieved by disabling Highcharts series grouping, ensuring each category band contains
     * a single centered bar or column rather than multiple grouped bars.
     */
    
    /**
     * Checks whether a widget or dashboard model already has a specific event handler attached.
     * Supports both Prism models and dashboard/widget models by inspecting the underlying event structures.
     *
     * @param {Object} model - The widget or dashboard model.
     * @param {string} eventName - The name of the event to check for.
     * @param {Function} handler - The handler function to search for.
     * @returns {boolean} True if the handler is already registered for the event, false otherwise.
     */
    function hasEventHandler(model, eventName, handler) {
      var eventHandlers = model.$ngscope // Check if Prism model
        ? ($$get(prism, '$ngscope.$$listeners.' + eventName) || []) // Prism model handler list
        : model.$$eventHandlers(eventName); // Dashboard or widget model handler list
    
      return eventHandlers.indexOf(handler) >= 0;
    }
    
    /**
     * Finds a panel object by its title, ignoring case.
     *
     * @param {Array} panels - The array of panels from widget.metadata.panels.
     * @param {string} title - The panel title to match.
     * @returns {Object|undefined} The matching panel, or undefined if not found.
     */
    function findPanelByTitle(panels, title) {
      var normalizedTitle = String(title || '').toLowerCase();
      return panels.find(function (panel) {
        return panel &&
               typeof panel.title === 'string' &&
               panel.title.toLowerCase() === normalizedTitle;
      });
    }
    
    /**
     * Event handler for the Highcharts 'beforeviewloaded' event on a widget.
     * Disables series grouping when specific conditions are met to center bars/columns.
     *
     * Conditions for applying the change:
     *  - The widget is a bar chart or column chart.
     *  - The Break By panel exists but has no items, or does not exist at all.
     *  - The Categories panel exists but has no items, or does not exist at all.
     *  - The Values panel exists and contains one or more items.
     *
     * @param {Object} widget - The Sisense widget model.
     * @param {Object} env - The environment object containing Highcharts options.
     */
    function onBeforeviewloadedCenter(widget, env) {
      var chartOptions = (env && env.options) ? env.options : {};
      try {
        var panels = (widget.metadata && widget.metadata.panels) || [];
    
        // Step 1: Determine if the widget is of the correct type
        var isBarOrColumnChart =
          widget.type === 'chart/bar' || widget.type === 'chart/column';
    
        // Step 2: Retrieve the Break By, Categories, and Values panels
        var breakByPanel    = findPanelByTitle(panels, 'break by');
        var categoriesPanel = findPanelByTitle(panels, 'categories');
        var valuesPanel     = findPanelByTitle(panels, 'values');
    
        // Step 3: Determine panel contents
        var breakByIsEmpty =
          !breakByPanel || !Array.isArray(breakByPanel.items) || breakByPanel.items.length === 0;
    
        var categoriesIsEmpty =
          !categoriesPanel || !Array.isArray(categoriesPanel.items) || categoriesPanel.items.length === 0;
    
        var valuesHasItems =
          !!valuesPanel && Array.isArray(valuesPanel.items) && valuesPanel.items.length > 0;
    
        // Step 4: Apply grouping change only when all conditions are met
        if (isBarOrColumnChart && breakByIsEmpty && categoriesIsEmpty && valuesHasItems) {
          chartOptions.plotOptions = chartOptions.plotOptions || {};
          chartOptions.plotOptions.series = Object.assign({}, chartOptions.plotOptions.series, {
            grouping: false
          });
        }
      } catch (_) {
        // Do nothing if metadata structure is unexpected
      }
    }
    
    /**
     * Registers the beforeviewloadedCenter handler on widgets as they initialize.
     */
    prism.on('dashboardloaded', function (e, args) {
      args.dashboard.on('widgetinitialized', function (_, initArgs) {
        var widget = initArgs && initArgs.widget;
        if (!widget || !widget.on) return;
    
        if (!hasEventHandler(widget, 'beforeviewloaded', onBeforeviewloadedCenter)) {
          widget.on('beforeviewloaded', onBeforeviewloadedCenter);
        }
      });
    });