Skip to content

Instantly share code, notes, and snippets.

@maxim
Created June 26, 2011 21:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save maxim/1047987 to your computer and use it in GitHub Desktop.
Save maxim/1047987 to your computer and use it in GitHub Desktop.
Progressify - a progress bar approximator for smoothness
# The point of this script is to try and produce as smooth as possible
# progress bar when the real progress values you get are at uneven intervals and/or scarce.
#
# For example you know that 10% is completed after 5 seconds, and then after another 10 seconds
# you know that 50% is completed. You don't have any more actual information, however you would like
# to draw a bar that grows as smooth as possible.
#
# Here I'm using a simple technique where every actual progress point adds a point
# on a plot of progress x time. Between every 2 points there assumed to be a straight line,
# and the average of slopes of all lines created by points is used to approximate a
# "smoother" progress.
#
# A simple heuristic is used to prevent progress from ever degressing or going above 100.
#
# For demo purposes I create server and client threads, server adds "actual" progress to a
# global variable, while client is polling that variable at even intervals and passing it
# onto Progressify, which returns approximated progress based on what it knows so far.
#
# This should really be implemented in javascript for front-end usage.
module Progressify
STARTING_SLOPE = 4
Point = Struct.new(:duration, :progress)
class Plot
def initialize
@points = []
@prev_progress = 0.0
end
def add_point(duration, progress)
if actual_progress_increased?(progress)
@points << Point.new(duration, progress)
end
end
def progress_at(duration)
new_progress = if !@points.empty? && @points.last.progress >= 100
100
else
calc_progress(duration)
end
new_progress = [@prev_progress, new_progress.round].max
@prev_progress = new_progress
new_progress
end
private
def calc_progress(duration)
[99, (duration * average_slope)].min
end
def actual_progress_increased?(progress)
prev_progress = @points.last ? @points.last.progress : 0
progress > prev_progress
end
def slopes
slopes = [STARTING_SLOPE]
@points.each_with_index do |point, index|
prev_duration = (index == 0 ? 0 : @points[index - 1].duration)
prev_progress = (index == 0 ? 0 : @points[index - 1].progress)
slopes << ((point.progress.to_f - prev_progress.to_f) / (point.duration.to_f - prev_duration.to_f))
end
slopes
end
def average_slope
result = slopes.inject(0){|sum, slope| sum += slope}
result /= slopes.size.to_f
end
end
end
# sleep(seconds) progress(%)
sample1 = [ [2, 9 ],
[5, 50 ],
[5, 75 ],
[3, 100 ] ]
sample2 = [ [5, 15 ],
[2, 30 ],
[4, 45 ],
[3, 65 ],
[6, 100 ] ]
sample3 = [ [1, 1 ],
[5, 50 ],
[5, 100 ] ]
sample4 = [ [1, 0 ],
[10, 25 ],
[2, 100 ] ]
# DEMO CONFIG
sample = sample1
polling_interval = 0.1 # seconds
display_style = :bar # :print or :bar
bar_length = 60
$progress = 0
server = Thread.new do
sample.each do |pair|
sleep pair.first
$progress = pair.last
end
end
client = Thread.new do
progressify = Progressify::Plot.new
polling_duration = 0.0
while($progress < 100) do
sleep polling_interval
polling_duration += polling_interval
progressify.add_point(polling_duration, $progress)
p = progressify.progress_at(polling_duration)
if display_style == :bar
print( ('=' * (p / 100.0 * bar_length.to_f) + "> %#{p}").ljust(bar_length, '_') + "\r" )
$stdout.flush
else
puts polling_duration.round.to_s.ljust(2) + " sec: actual: #{($progress.to_s + ',').ljust(3)} approximated: #{p}"
end
end
puts "\n"
end
server.join
client.join
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment