Skip to content

Instantly share code, notes, and snippets.

@mdirienzo
Last active December 2, 2020 08:04
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save mdirienzo/6716905 to your computer and use it in GitHub Desktop.
Save mdirienzo/6716905 to your computer and use it in GitHub Desktop.
Progress Bars - An animated progress bar widget for Dashing.

Progress Bar Widget

Description

A widget made for Dashing. This widget shows multiple animated progress bars and reacts dynamically to new information being passed in. 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.

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.

Preview

A screenshot showing multiple variations of the widgetA screenshot showing multiple variations of the widget. A live demo is available here

Dependencies

Needs a job that sends data to the widget.

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="ProgressBars" data-title="Project Bars"></div>
</li>

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>} 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

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

class Dashing.ProgressBars 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-progress-bar")
if elem.length
@animateProgressBarContent(elem[0], parseFloat(elem[0].style.width),
parseFloat(item.progress), 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)
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-progress-bar") )
innerProgressBar = $("<div/>")
.attr("class", "inner-progress-bar")
.text("0%")
innerProgressBar.css("width", "0%")
# Put it all together.
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.
# @from - the value that the element starts at.
# @to - the value that the element is going to.
# @baseDuration - the minimum time the animation will perform.
# /
animateProgressBarContent: (element, from, to, baseDuration) ->
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
interval = setInterval(
->
currentValue += valueIncrement
if Math.abs(currentValue - from) >= Math.abs(endpointDifference)
setProgressBarValue(element, to)
clearInterval(interval)
else
setProgressBarValue(element, currentValue)
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.
# /
setProgressBarValue = (element, value) ->
if (value > 100)
value = 100
else if (value < 0)
value = 0
element.textContent = Math.floor(value) + "%"
element.style.width = value + "%"
#***/
# 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;
$base-color: $blue;
$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%);
$text-color: $base-color-lightest;
// ----------------------------------------------------------------------------
// Widget-project-completion styles
// ----------------------------------------------------------------------------
.widget.widget-progress-bars {
height: 100%;
width: 100%;
padding: 5px;
position:relative;
background-color: $base-color;
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-progress-bar {
display:inline-block;
width: 65%;
vertical-align: middle;
border: ($row-size / 3) solid $base-color-dark;
border-radius: 2 * $row-size;
background-color: $base-color-lighter;
.inner-progress-bar {
background-color: $base-color-dark;
border-radius: $row-size / 2;
color: $white;
}
}
.zebra-stripe {
background-color: $base-color-light;
}
}
@Zerotouch
Copy link

Zerotouch commented Feb 9, 2017

I've noticed an issue with the sizing of the widget overall and the spacing between items. It seems to occur under both Dashing and the Smashing fork, and I can't find the CSS that might be causing this.

i have removed the bolded ".widget" part from the scss and seems working to me:
UPDATE: i also modified the padding, also bolded
.widget.widget-progress-bars {
height: 100%;
width: 100%;
padding-right: 5px;
padding-left: 5px;
padding-top: 0px;
padding-bottom: 0px;

position:relative;
background-color: $base-color;
vertical-align: baseline;

@boolsee
Copy link

boolsee commented Dec 2, 2020

I've noticed an issue with the sizing of the widget overall and the spacing between items. It seems to occur under both Dashing and the Smashing fork, and I can't find the CSS that might be causing this.

I found 'Demo' has a 'class=gs_w', so I added it to my sample dashboard like below and finally got a row-containment of full width .

<li data-row="1" data-col="1" data-sizex="1" data-sizey="1" class="gs_w">
<div data-id="progress_bars" data-view="ProgressBars" data-title="Project Bars"></div>
</li>

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