Progress Bars - An animated progress bar widget for Dashing.
// ----------------------------------------------------------------------------
// 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;
}
}
<h1 class="title" data-bind="title"></h1>
<div class="rows-container">
</div>
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)
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.
A screenshot showing multiple variations of the widget. A live demo is available here
Needs a job that sends data to the widget.
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.