chieffancypants / angular-loading-bar

A fully automatic loading / progress bar for your angular apps.
https://chieffancypants.github.io/angular-loading-bar/
MIT License
5.17k stars 682 forks source link

Random increment math #114

Open JesseBuesking opened 10 years ago

JesseBuesking commented 10 years ago

I was just looking through the code and saw your comments for the random incrementing logic where you say you'd like to "do this mathematically...".

A Linear formula:

If you want to follow a similar formula going forward, I think that a simple application of the slope-intercept to decrease the increment linearly would probably be fine. Given you want to start with a maximum increment of 6.0% and end up with an increment of 0.5%, you could use the following:

function maxIncrement(stat) {
  var x1=0,
      y1=6.0,
      x2=99,
      y2=0.5,
      b=0, // intercept
      m=0; // slope
  m = (y1-y2)/(x1-x2);
  b = y1-(m*x1);
  // return the y-value from the formula y = m * x + b
  return m*stat+b;
}

Which gives the following values:

maxIncrement(0)
> 6
maxIncrement(1)
> 5.944444444444445
maxIncrement(2)
> 5.888888888888889
...
maxIncrement(97)
> 0.6111111111111116
maxIncrement(98)
> 0.5555555555555562
maxIncrement(99)
> 0.5

That function is a bit verbose when we leave all the math inside, but you could simplify it as follows:

function maxIncrement(stat) {
  return (5.5/-99)*stat+(6.0);
}

Now, your code actually uses the value we're finding above as the maximum threshold, where you choose a random value between 0 and the maximum. To re-enable this logic, we can create another method to handle it as follows:

function incrementer(stat) {
  var m = maxIncrement(stat);
  return (Math.random() * m) / 100;
}

Last, we still need to handle the case greater than or equal to 99%. Here's what it would be in it's final form:

if (stat >= 0.99) {
  rnd = 0;
} else {
  rnd = incrementer(stat);
}

To visualize all of this a bit better, check out what it looks like on Wolfram Alpha.

linear

Using the image above, we're picking an x value (stat) and finding the y value represented by the blue sloping line. This value becomes our upper bound. We then choose a random value between [0, 1) using Math.random(), and multiply to get a new value somewhere between 0 and our upper bound.

Everything together:

function maxIncrement(stat) {
  return (5.5/-99)*stat+(6.0);
}
function incrementer(stat) {
  var m = maxIncrement(stat);
  return (Math.random() * m) / 100;
}
if (stat >= 0.99) {
  rnd = 0;
} else {
  rnd = incrementer(stat);
}

Obviously there's no need to keep those all as method calls, so feel free to inline things as you see fit.

Note: I deviated from your logic slightly in that you don't choose a random multiplier when stat >= 0.9 && stat < 0.99, whereas I do.

A Non-Linear formula:

If you're looking for something non-linear like an exponential decay, take a look at this Wolfram Alpha. The only adjustment to the formula above would be:

function maxIncrement(stat) {
  return 6.0*Math.exp(-5.0*(stat/100));
}

Which gives the following values:

maxIncrement(0)
> 6
maxIncrement(1)
> 5.707376547004284
maxIncrement(2)
> 5.4290245082157575
...
maxIncrement(97)
> 0.046970265295354655
maxIncrement(98)
> 0.04467949842554603
maxIncrement(99)
> 0.042500453574312716

You can see that compared to the linear formula this slows down much more rapidly and has a longer tail.

non-linear

Sorry for the long post, this ended up growing more rapidly than I had originally anticipated.

MattiJarvinen commented 8 years ago

Some user experience studies suggest that progress should be initially slow and speed up towards the end. This is though to implement in practice because we don't necessarily know the duration of the request.

http://uxmovement.com/buttons/how-to-make-progress-bars-feel-faster-to-users/

faceleg commented 8 years ago

This looks interesting, if anyone wants to have a go, it'd be great to see what it might look like.