Created
June 26, 2011 21:22
-
-
Save maxim/1047987 to your computer and use it in GitHub Desktop.
Progressify - a progress bar approximator for smoothness
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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