Skip to content

Instantly share code, notes, and snippets.

@yannrouillard
Forked from mdirienzo/README.md
Last active March 6, 2019 21:27
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yannrouillard/48cb4238f2be032c7a98 to your computer and use it in GitHub Desktop.
Save yannrouillard/48cb4238f2be032c7a98 to your computer and use it in GitHub Desktop.

Hot Progress Bar Widget

Description

This widget is a fork of the original Progress Bar Widget, a widget made for Dashing.

The original widget shows multiple animated progress bars and reacts dynamically to new information being passed in. This forked widget adds the possibility to define warning and critical thresholds for each progress bars, which will change their color depending on the threshold reached.

Anything with a current state and with a projected max/goal state can easily be represented with this widget. Some sample ideas would be to show progress, completion, capacity, load, fundraising, and much more.

With the threashold feature, it is typically aimed at being used in simple green/orange/red monitoring dashboards.

Features

  • Animating progress bars - Both the number and bar will grow or shrink based on new data that is being passed to it.
  • Responsive Design - Allows the widget to be resized to any height or width and still fit appropriately. The progress bars will split up all available space amongst each other, squeezing in when additional progress bars fill the widget.
  • Easy Customization - Change the base color in one line in the scss and have the entire widget color scheme react. The font size and progress bar size are handled by a single magic variable in the scss that will scale each bar up proportionally.
  • As a bonus, this widget can also play a sound when status is modified. Just add one or more data-*status*sound attributes with the value set to an absolute or relative url pointing to an audio file. status must be one of ok, critical, warning or unknown.

Usage

With this sample widget code in your dashboard:

<li data-row="1" data-col="1" data-sizex="2" data-sizey="1">
  <div data-id="progress_bars" data-view="HotProgressBars" data-title="Project Bars" data-zebra="1" data-criticalsound="/mp3/critical.mp3"></div>
</li>

You can disable the zebra stripping feature by removing or setting data-zebra attribute to 0.

You can send an event through a job like the following: send_event( 'progress_bars', {title: "", progress_items: []} )

progress_items is an array of hashes that follow this design: {name: <value>, progress: <value>, critical: <value>, warning: <value>, localScope: <value>}

  • The name key can be any unique string that describes the bar.
  • The progress variable is a value from 0-100 that will represent the percentage of the bar that should be filled. Valid inputs include: 24, "24", "24%", 24.04

critical, warning and localScope are optional values with the following meaning:

  • critical defines the critical threshold, the progress bar will turn red if the value reaches this threashold,
  • warning defines the warning threshold, the progress bar will turn yellow if the value reaches this threshold,
  • localScope can be 0 or 1 and configures whether or not the whole widget should change its color if a threshold is reached for this progress bar.

Sending a request to a web service for a JSON response or reading from a file can produce this information easily.

class Dashing.HotProgressBars extends Dashing.Widget
@accessor 'title'
ready: ->
@drawWidget( @get('progress_items') )
onData: (eventData) ->
@drawWidget(eventData.progress_items)
drawWidget: (progress_items) ->
container = $(@node)
rowsContainer = container.find('.rows-container')
if progress_items.length == 0
rowsContainer.empty()
else
# Float value used to scale the rows to use the entire space of the widget
rowHeight = 100 / progress_items.length
counter = 0
@clearIntervals()
# Add or move rows for each project. Checks first if the row already exists.
progress_items.forEach (item) =>
normalizedItemName = item.name.replace(/\W+/g, "_")
referenceRow = rowsContainer.children().eq(counter)
existingRow = rowsContainer.find("."+normalizedItemName)
if existingRow.length
if referenceRow.attr("class").indexOf(normalizedItemName) == -1
existingRow.detach().insertBefore(referenceRow)
existingRow.hide().fadeIn(1200)
else
row = createRow(item)
if referenceRow.length
row.insertBefore(referenceRow)
else
rowsContainer.append(row)
row.hide().fadeIn(1200)
elem = rowsContainer.find("."+normalizedItemName+" .inner-hot-progress-bar")
if elem.length
@animateProgressBarContent(elem[0], item, 1000)
++counter
# Remove any nodes that were not in the new data, these will be the rows
# at the end of the widget.
currentNode = rowsContainer.children().eq(counter-1)
while currentNode.next().length
currentNode = currentNode.next()
currentNode.fadeOut(100, -> $(this).remove() )
# Set the height after rows were added/removed.
rows = rowsContainer.children()
percentageOfTotalHeight = 100 / progress_items.length
applyCorrectedRowHeight(rows, percentageOfTotalHeight)
if @zebra
applyZebraStriping(rows)
#***/
# Create a JQuery row object with the proper structure and base
# settings for the item passed in.
#
# The Row DOM Hierarchy:
# Row
# Row Content (here so we can use vertical alignment)
# Project Name
# Outer Bar Container (The border and background)
# Inner Bar Container (The progress and text)
#
# @item - object representing an item and it's progress
# /
createRow = (item) ->
row = ( $("<div/>")
.attr("class", "row " + item.name.replace(/\W+/g, "_") ) )
rowContent = ( $("<div/>")
.attr("class", "row-content") )
projectName = ( $("<div/>")
.attr("class", "project-name")
.text(item.name)
.attr("title", item.name) )
outerProgressBar = ( $("<div/>")
.attr("class", "outer-hot-progress-bar") )
innerProgressBar = $("<div/>")
.attr("class", "inner-hot-progress-bar")
innerProgressBar.css("width", "0%")
progressBarValue = $("<p/>").text("0%")
# Put it all together.
innerProgressBar.append(progressBarValue)
outerProgressBar.append(innerProgressBar)
rowContent.append(projectName)
rowContent.append(outerProgressBar)
row.append(rowContent)
return row
#***/
# Does calculations for the animation and sets up the javascript
# interval to perform the animation.
#
# @element - element that is going to be animated.
# @progress_item - an item of the progress_items data received
# @baseDuration - the minimum time the animation will perform.
# /
animateProgressBarContent: (element, item, baseDuration) ->
from = parseFloat(element.style.width)
to = parseFloat(item.progress)
endpointDifference = (to-from)
if endpointDifference != 0
currentValue = from
# Every x milliseconds, the function should run.
stepInterval = 16.667
# Change the duration based on the distance between points.
duration = baseDuration + Math.abs(endpointDifference) * 25
numberOfSteps = duration / stepInterval
valueIncrement = endpointDifference / numberOfSteps
progressBars = this
interval = setInterval(
->
currentValue += valueIncrement
if Math.abs(currentValue - from) >= Math.abs(endpointDifference)
setHotProgressBarValue(element, to, item.warning, item.critical, item.localScope)
clearInterval(interval)
else
setHotProgressBarValue(element, currentValue, item.warning, item.critical, item.localScope)
updateHotProgressBarStatus(progressBars)
stepInterval)
@addInterval(interval)
#***/
# Sets the text and width of the element in question to the specified value
# after making sure it is bounded between [0-100]
#
# @element - element to be set
# @value - the numeric value to set the element to. This can be a float.
# @warningThreshold - the treshold at which display a warning visual alert
# @criticalThreshold - the treshold at which display a critical visual alert
# @localScope - whether this item can impact the global status of the widget
# /
setHotProgressBarValue = (element, value, warningThreshold, criticalThreshold, localScope) ->
if (value > 100)
value = 100
else if (value < 0)
value = 0
element.textContent = Math.floor(value) + "%"
element.style.width = value + "%"
newStatus = switch
when criticalThreshold and value >= criticalThreshold then 'critical'
when warningThreshold and value >= warningThreshold then 'warning'
else 'ok'
for status in ['ok', 'critical', 'warning']
do (status) ->
match = (newStatus == status)
$(element).toggleClass("inner-hot-progress-bar-#{status}", match)
$(element).parent().toggleClass("outer-hot-progress-bar-#{status}", match)
$(element).toggleClass("global-alert", not localScope)
#***/
# Update the widget background accorrding to the progress items status
#
# @progressBars - DOM element corresponding to the widget
# /
updateHotProgressBarStatus = (progressBars) ->
progressBars_node = $(progressBars.node)
overallStatus = switch
when progressBars_node.find(".inner-hot-progress-bar-critical.global-alert").length then 'critical'
when progressBars_node.find(".inner-hot-progress-bar-warning.global-alert").length then 'warning'
else 'ok'
lastOverallStatus = progressBars.lastOverallStatus
if lastOverallStatus != overallStatus
progressBars.lastOverallStatus = overallStatus
for status in ['ok', 'critical', 'warning']
do (status) ->
progressBars_node.toggleClass("widget-hot-progress-bars-#{status}", overallStatus == status)
audiosound = progressBars[overallStatus + 'sound']
audioplayer = new Audio(audiosound) if audiosound?
if audioplayer
audioplayer.play()
#***/
# Applies a percentage-based row height to the list of rows passed in.
#
# @rows - the elements to apply this height value to
# @percentageOfTotalHeight - The height to be applied to each row.
# /
applyCorrectedRowHeight = (rows, percentageOfTotalHeight) ->
height = percentageOfTotalHeight + "%"
for row in rows
row.style.height = height
#***/
# Adds a class to every other row to change the background color. This
# was done mainly for readability.
#
# @rows - list of elements to run zebra-striping on
# /
applyZebraStriping = (rows) ->
isZebraStripe = false
for row in rows
# In case elements are moved around, we don't want them to retain this.
row.classList.remove("zebra-stripe")
if isZebraStripe
row.classList.add("zebra-stripe")
isZebraStripe = !isZebraStripe
#***/
# Stops all javascript intervals from running and clears the list.
#/
clearIntervals: ->
if @intervalList
for interval in @intervalList
clearInterval(interval)
@intervalList = []
#***/
# Adds a javascript interval to a list so that it can be tracked and cleared
# ahead of time if the need arises.
#
# @interval - the javascript interval to add
#/
addInterval: (interval) ->
if !@intervalList
@intervalList = []
@intervalList.push(interval)
<h1 class="title" data-bind="title"></h1>
<div class="rows-container">
</div>
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
// row-size is a magic number used for scaling. It will make things bigger
// or smaller but always in proportion with each other. Feel free to change
// this to reflect your personal needs.
$row-size: 0.7em;
$blue: #2db4d4;
$white: #ffffff;
$green: #00C176;
$base-color: $green;
$base-color-dark: darken($base-color, 10%);
$base-color-light: lighten($base-color, 10%);
$base-color-lighter: lighten($base-color, 25%);
$base-color-lightest: lighten($base-color, 35%);
$warning-color: #FABE28;
$warning-color-dark: darken($warning-color, 10%);
$warning-color-light: lighten($warning-color, 10%);
$warning-color-lighter: lighten($warning-color, 25%);
$critical-color: #FF003C;
$critical-color-dark: darken($critical-color, 10%);
$critical-color-light: lighten($critical-color, 10%);
$critical-color-lighter: lighten($critical-color, 25%);
// $text-color: $base-color-lightest;
$text-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-project-completion styles
// ----------------------------------------------------------------------------
.widget.widget-hot-progress-bars {
height: 100%;
width: 100%;
padding: 5px;
position:relative;
vertical-align: baseline;
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing:border-box;
}
.title {
color: $text-color;
margin-bottom: 5px;
}
.rows-container {
height: 85%;
width:100%;
color: $text-color;
font-size: $row-size;
text-align:center;
}
.row {
height:0%;
width:100%;
vertical-align: middle;
display:table;
transition-property: height;
transition-duration: 0.3s;
transition-timing-function: linear;
}
.row-content {
padding-left: 5px;
display:table-cell;
vertical-align: middle;
}
.project-name {
display:inline-block;
width:35%;
padding-right: $row-size;
text-align: left;
vertical-align: middle;
text-overflow: ellipsis;
overflow:hidden;
white-space: nowrap;
}
.outer-hot-progress-bar {
display:inline-block;
width: 65%;
vertical-align: middle;
border-radius: 2 * $row-size;
border-width: ($row-size / 3);
border-style: solid;
.inner-hot-progress-bar {
border-radius: $row-size / 2;
color: $white;
}
.inner-hot-progress-bar-ok {
background-color: $base-color-dark;
}
.inner-hot-progress-bar-warning {
background-color: $warning-color-dark;
}
.inner-hot-progress-bar-critical {
background-color: $critical-color-dark;
}
}
.outer-hot-progress-bar-ok {
background-color: $base-color-lighter;
border-color: $base-color-dark;
}
.outer-hot-progress-bar-warning {
background-color: $warning-color-lighter;
border-color: $warning-color-dark;
}
.outer-hot-progress-bar-critical {
background-color: $critical-color-lighter;
border-color: $critical-color-dark;
}
.zebra-stripe {
#background-color: $base-color-light;
background-color: $base-color;
}
}
.widget.widget-hot-progress-bars-ok {
background-color: $base-color;
.zebra-stripe {
background-color: $base-color-light;
}
}
.widget.widget-hot-progress-bars-warning {
background-color: $warning-color;
.zebra-stripe {
background-color: $warning-color-light;
}
}
.widget.widget-hot-progress-bars-critical {
background-color: $critical-color;
.zebra-stripe {
background-color: $critical-color-light;
}
}
@larrycai
Copy link

will be nice if you can have snapshot for the result

@kalenwatermeyer
Copy link

Hi @larrycai. I've integrated the Hot Progress Bars widget, and an example screenshot is shown below:

Hot Progress Bars example - screenshot

I send data from Windows to the widget using cURL. In this example, my cURL command is:

<your-curl.exe> -d "{\"auth_token\": \"<your-auth-token>\", \"progress_items\":[{\"name\": \"Critical item:\", \"progress\": \"50\", \"warning\": \"30\", \"critical\": \"45\", \"localScope\": \"1\"}, {\"name\": \"Warning item:\", \"progress\": \"35\", \"warning\": \"30\", \"critical\": \"45\", \"localScope\": \"1\"}, {\"name\": \"Regular item:\", \"progress\": \"15\", \"warning\": \"30\", \"critical\": \"45\", \"localScope\": \"1\"}]  }" <http://your-dashboard:<port>/widgets/hot_progress_bars

Hope this helps!

Copy link

ghost commented Nov 13, 2015

What is the easiest way to reverse the colors on this widget so that closer to 100% is green?

@ifnull
Copy link

ifnull commented Dec 27, 2015

@aspence7 Just change the conditionals in the newStatus switch.

diff --git a/widgets/hot_progress_bars/hot_progress_bars.coffee b/widgets/hot_progress_bars/hot_progress_bars.coffee
index 537e0c5..156536c 100644
--- a/widgets/hot_progress_bars/hot_progress_bars.coffee
+++ b/widgets/hot_progress_bars/hot_progress_bars.coffee
@@ -163,8 +163,8 @@ class Dashing.HotProgressBars extends Dashing.Widget
     element.style.width = value + "%"

     newStatus = switch
-      when criticalThreshold and value >= criticalThreshold then 'critical'
-      when warningThreshold and value >= warningThreshold then 'warning'
+      when criticalThreshold and value <= criticalThreshold then 'critical'
+      when warningThreshold and value <= warningThreshold then 'warning'
       else 'ok'

     for status in ['ok', 'critical', 'warning']
diff --git a/widgets/hot_progress_bars/hot_progress_bars.scss b/widgets/hot_progress_bars/hot_progress_bars.scss

@kondalonline
Copy link

kondalonline commented Nov 26, 2016

@kalenwatermeyer
@yannrouillard

I just created a widget and added the coffee,html and scss file as mentioned above. Also added a list item to dashboard.

Dashboard widget is dumping this code below. Any suggestion as what might have gone wrong. Other widgets are working fine on the same dashboard.

["str", [Tilt::StringTemplate]]["erb", [Tilt::ErubisTemplate, Tilt::ERBTemplate]]["rhtml", [Tilt::ErubisTemplate, Tilt::ERBTemplate]]["erubis", [Tilt::ErubisTemplate]]["etn", [Tilt::EtanniTemplate]]["etanni", [Tilt::EtanniTemplate]]["haml", [Tilt::HamlTemplate]]["mab", [Tilt::MarkabyTemplate]]["liquid", [Tilt::LiquidTemplate]]["radius", [Tilt::RadiusTemplate]]["markdown", [Tilt::RedcarpetTemplate, Tilt::RedcarpetTemplate::Redcarpet2, Tilt::RedcarpetTemplate::Redcarpet1, Tilt::RDiscountTemplate, Tilt::BlueClothTemplate, Tilt::KramdownTemplate, Tilt::MarukuTemplate]]["mkd", [Tilt::RedcarpetTemplate, Tilt::RedcarpetTemplate::Redcarpet2, Tilt::RedcarpetTemplate::Redcarpet1, Tilt::RDiscountTemplate, Tilt::BlueClothTemplate, Tilt::KramdownTemplate, Tilt::MarukuTemplate]]["md", [Tilt::RedcarpetTemplate, Tilt::RedcarpetTemplate::Redcarpet2, Tilt::RedcarpetTemplate::Redcarpet1, Tilt::RDiscountTemplate, Tilt::BlueClothTemplate, Tilt::KramdownTemplate, Tilt::MarukuTemplate]]["textile", [Tilt::RedClothTemplate]]["rdoc", [Tilt::RDocTemplate]]["wiki", [Tilt::WikiClothTemplate, Tilt::CreoleTemplate]]["creole", [Tilt::CreoleTemplate]]["mediawiki", [Tilt::WikiClothTemplate]]["mw", [Tilt::WikiClothTemplate]]["ad", [Tilt::AsciidoctorTemplate]]["adoc", [Tilt::AsciidoctorTemplate]]["asciidoc", [Tilt::AsciidoctorTemplate]]["html", [Tilt::PlainTemplate]]

@kondalonline
Copy link

@ALL.
I made a typo while creating the widget. I have created hot_progress_bar widget instead of hot_progress_bars

It's working fine now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment