Animated Target vs Actual Progress Bar with BloX [Linux]
Introduction
This article demonstrates how to build an animated progress bar that visualises actual performance against a target using a BloX widget in Sisense. Sisense's native indicator widgets offer basic target comparisons, but they provide limited control over layout, colour logic, and animation. This BloX solution displays the actual value, the target value, and a dynamically filled progress bar – all within a single compact widget. The bar animates on load, fills proportionally based on the actual-to-target ratio, and changes colour automatically depending on performance:
Green when the target has been met or exceeded (≥ 100%).
Blue for normal progress (between 30% and 100%).
Red when attainment is critically low (≤ 30%).
This makes it ideal for quota attainment tracking, budget consumption, project completion, or any use case where progress toward a goal needs to be communicated at a glance. The solution is tested on Sisense L2026.1.2 and works for both cloud and on-premises deployments.
Step-by-Step Guide
1. Prepare your environment
Open an existing dashboard or create a new one. You will need two measures available in your data model:
An actual measure (e.g. Total Actual, Total Revenue, Units Sold).
A target measure (e.g. Total Target, Quota, Budget).
Both measures will be added as separate panels in the BloX widget.
2. Add a BloX widget
Click + Add Widget on your dashboard and select BloX from the widget type list. Open the BloX editor.
3. Set up your data panel
In the BloX data panel, add two panels:
Panel 1: Your actual measure – name it Total Actual.
Panel 2: Your target measure – name it Total Target.
These names are referenced in the BloX body using the {panel: <Panel Name>} syntax and must match exactly.
4. Paste the full BloX JSON
Replace the contents of the BloX editor with the following JSON:
{
"script": "setTimeout(function() { $('.blox-goal-container').each(function() { var $this = $(this); var actualText = $this.find('.actual-val').text().replace(/[^0-9.-]+/g, ''); var targetText = $this.find('.target-val').text().replace(/[^0-9.-]+/g, ''); var actual = parseFloat(actualText) || 0; var target = parseFloat(targetText) || 1; var pct = (actual / target) * 100; if (pct < 0) pct = 0; if (pct > 100) pct = 100; var barColor = '#3498db'; if (pct >= 100) barColor = '#2ecc71'; else if (pct <= 30) barColor = '#e74c3c'; var $fill = $this.find('.blox-progress-fill'); $fill.css('background-color', barColor); $fill.animate({ width: pct + '%' }, 1500); }); }, 500);",
"showCarousel": false,
"body": [
{
"type": "Container",
"class": "blox-goal-container",
"style": {
"background-color": "#ffffff",
"padding": "25px"
},
"items": [
{
"type": "TextBlock",
"text": "Department: {panel: Department}",
"size": "small",
"weight": "bolder",
"color": "dark",
"isSubtle": true
},
{
"type": "ColumnSet",
"style": {
"margin-top": "15px",
"margin-bottom": "15px"
},
"columns": [
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"class": "actual-val",
"text": "{panel:Total Actual} ",
"size": "extraLarge",
"weight": "bolder",
"color": "dark"
}
]
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"text": "Target: ",
"size": "medium",
"isSubtle": true,
"horizontalAlignment": "right",
"style": {
"margin-top": "10px"
}
}
]
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"class": "target-val",
"text": "{panel:Total Target} ",
"size": "medium",
"weight": "bolder",
"horizontalAlignment": "right",
"style": {
"margin-top": "10px"
}
}
]
}
]
},
{
"type": "Container",
"class": "blox-progress-track",
"style": {
"background-color": "#ecf0f1",
"border-radius": "10px",
"height": "16px",
"width": "100%",
"overflow": "hidden"
},
"items": [
{
"type": "Container",
"class": "blox-progress-fill",
"style": {
"background-color": "#3498db",
"height": "16px",
"width": "0%",
"border-radius": "10px"
},
"items": []
}
]
}
]
}
],
"actions": []
}5. Adapt the JSON to your data
There are three areas you will typically need to customise:
Panel references – Replace {panel:Total Actual} and {panel:Total Target} with the exact panel names as they appear in your BloX data panel. The names must match exactly, including spacing.
Card label – Update the "text" value in the first TextBlock (currently {panel:Department}) to reflect your metric/panel name.
Colour thresholds – The script uses three colour zones by default. To adjust the thresholds, locate the following lines in the script field and change the numeric values:
if (pct >= 100) → bar turns green. Change 100 to a different target attainment threshold if needed.
else if (pct <= 30) → bar turns red. Change 30 to raise or lower the danger zone.
6. Adjust the animation speed
The bar animates over 1500 milliseconds (1.5 seconds) by default. To change this, find 1500 in the script field and replace it with your preferred duration in milliseconds. For example, use 800 for a snappier animation or 3000 for a slower, more dramatic fill.
7. Save and test
Click Save in the BloX editor. The widget will render with the actual and target values displayed, and the progress bar will animate from 0% to the calculated attainment percentage. Applying dashboard filters will recalculate and re-animate the bar in real time.

Important notes
Formatted values: The regex replace(/[^0-9.-]+/g, '') strips currency symbols, thousand separators, and percentage signs before parsing. This means the script works correctly with values formatted as $1,234,567 or 1.5M – as long as the underlying number is present in the rendered text. Very large abbreviated values (e.g. 1.2B) may not parse correctly; in those cases, consider formatting the panel value as a plain number.
Zero target guard: The script defaults the target to 1 if the parsed target value is 0 or missing (var target = parseFloat(targetText) || 1). This prevents a division-by-zero error. If a zero target is a valid scenario in your data, add explicit handling for that case.
Multiple bar groups on one widget: Each blox-goal-container is processed independently, so multiple bar groups can coexist in the same widget without interfering with each other.
Export behaviour: Because the progress bar is rendered via DOM manipulation and animation, PDF and image exports will capture the bar at whatever fill state it is in at the moment of export – typically fully filled, since the animation completes quickly.
Conclusion
By combining BloX's flexible JSON layout with a lightweight jQuery animation, this solution delivers a polished, data-driven progress bar that responds dynamically to dashboard filters and automatically signals performance status through colour. The pattern is straightforward to extend: additional metrics can be stacked as new cards, thresholds can be tuned to match business rules, and the label and colour scheme can be adapted to any use case – from sales quota tracking to project milestone completion.
References / Related content
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.