Progress bars

Warning

Progress bars displays are updated in the background while your code executes. This is done by having the display rendering code run on a separate thread. If you're using a single thread (see: https://docs.julialang.org/en/v1/manual/multi-threading/) you'll need to add a sleep(0.001) or yield() command inside your code whose progress you're monitoring to ensure that the display is updated correctly.

Overview

Progress bars! We all love progress bars, and Julia has some great progress bars packages. But this is Term, and Term too has its own progress bars API. We think you'll like it. If not, worry not! Term's progress bars play well ProgressLogging.jl, scroll to the bottom of this page!

Warning

Progress bars do some terminal magic that doesn't play well with how the docs are rendered here. If you want to see what progress bars actually look like, we encourage you to copy-paste the code below and try it out in your own console. Or head to github where you can find more examples.

So this is what a progress bar looks like in Term:

using Term.Progress

pbar = ProgressBar()
job = addjob!(pbar; N=5)
start!(pbar)
for i in 1:5
    update!(job)
    sleep(0.01)
    render(pbar)
end
stop!(pbar)



────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━                     2/5  40%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━              3/5  60%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━        4/5  80%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%

Let's see what's happening. We start by creating a progress bar

pbar = ProgressBar()

and then we add a "job"

job = addjob!(pbar; N=5)

what's that? Term's ProgressBars can handle multiple "jobs" simultaneously. So if you have multiple things you want to keep track of (e.g. multiple nested loops), you can have a job for each. Next we start the progress bar:

start!(pbar)

we update the job at each step of the loop and render the updated display:

    update!(job)
    render(pbar)

and finally we stop the whole thing:

stop!(pbar)

.

Okay, why do we need all of this starting and stopping business? Updating the job makes sense, we need to know when we made progress, but why do we need to start and stop the progress bar? Well, ProgressBar does a bit of terminal magic to ensure that any content you print to STDOUT is displayed without disrupting the progress bar's behavior, so we need to start and stop it to make sure that the magic happens and that the terminal is restored when we're done.

You might spot a problem: what if we forgot stop!, or our code errors before we reach it or something else like this? That's a problem, our terminal won't be restored. Well don't worry, there's a solution:

pbar = ProgressBar()
job1 = addjob!(pbar; N=5)
job2 = addjob!(pbar; N=10)

with(pbar) do
    for i in 1:10
        update!(job1)
        update!(job2)
        i % 3 == 0 && println("text appears here!")
        sleep(0.001)
    end
end





────────────────────── progress ───────────────────────
Running...  ━━━━━━                           1/5  20%
Running...  ━━━                             1/10  10%text appears here!
────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━                  5/10  50%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━                  5/10  50%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━                  5/10  50%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━               6/10  60%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━               6/10  60%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━               6/10  60%text appears here!
────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━            7/10  70%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━            7/10  70%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━            7/10  70%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━         8/10  80%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━         8/10  80%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━         8/10  80%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━      9/10  90%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━      9/10  90%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━      9/10  90%text appears here!
────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%


`println` vs `print`

You'll notice we've used println in the example above. Currently, using print will break the layout and the text won't be printed correctly. Please use println while using ProgressBar!

with takes care of it all. It starts the progress bar and stops it too! No matter what happens in the code inside the do block, with will stop the progress bar so we can use it with no fear. It also removes the need to explicitly call start!/stop!, which helps. We recommend that you always use with.

So with with we loose some of the boiler plate, but it's still a bit too much isn't it? It's cool if you need something specific, but if you want a simple progress bar to monitor progress in a loop perhaps you want something simpler. That's where @track comes in:

@track for i in 1:10
    sleep(0.01)
end



────────────────────── progress ───────────────────────
Running...  ━━━                             1/10  10%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━                       3/10  30%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━                  5/10  50%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━               6/10  60%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━         8/10  80%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  10/10 100%


As simple as that!

ProgressJob

There's just couple things you need to know about ProgessJob. First, you can set the expected number of steps in the progress bar. We've seen that before, it's the N in addjob!(pbar; N=5). You don't have to though, if you don't know how long your job's going to be then just leave it out! In that case you won't see a progress bar, but you can still see some progress:

pbar = ProgressBar(; columns=:spinner)
job = addjob!(pbar; description="how long is this")

with(pbar) do
    for i in 1:10
        update!(job)
        sleep(0.001)
    end
end



────────────────────── progress ───────────────────────
how long is this   ( ●    )   0────────────────────── progress ───────────────────────
how long is this   ( ●    )   9


What's up with that columns=:spinner? Read below. By the way, there's a few different kind of spinners

import Term.Progress: SPINNERS

for spinner in keys(SPINNERS)
    columns_kwargs = Dict(
        :SpinnerColumn => Dict(:spinnertype => spinner, :style=>"bold green"),
        :CompletedColumn => Dict(:style => "dim")
    )

    pbar = ProgressBar(; columns=:spinner, columns_kwargs=columns_kwargs)
    with(pbar) do
        job = addjob!(pbar; description="{orange1}$spinner...")
        for i in 1:3
            update!(job)
            sleep(.0025)
        end
    end
end
┌ Warning: Assignment to `pbar` in soft scope is ambiguous because a global variable by the same name exists: `pbar` will be treated as a new local. Disambiguate by using `local pbar` to suppress this warning or `global pbar` to assign to the existing global variable.
└ @ progressbars.md:123

────────────────────── progress ───────────────────────


────────────────────── progress ───────────────────────
greek...   ϴ   1




────────────────────── progress ───────────────────────






────────────────────── progress ───────────────────────






────────────────────── progress ───────────────────────






────────────────────── progress ───────────────────────






────────────────────── progress ───────────────────────




Options

The main way in which you can personalize progress bars display, is by choosing which columns to show (see below), but there's also a few additional parameters. You can use width to specify how wide the progress bar display should be, or set expand=true to make it fill up all available space. Also, sometimes you want to have a bunch of display bars to show progress in various bits of your code, but you don't want your final terminal display to be cluttered. In that case set transient=true and the progress bars will be erased when they're finished!

Columns

As you've seen, each progress bar display shows various bits of information: some text description, the progress bar itself, a counts bit... Each of these is a "column" (a subtype of AbstractColumn). There's many types of columns showing different kinds of information, and you can make your own too (see below). Term offers different presets columns layouts do display varying levels of detail:

for details in (:minimal, :default, :detailed)
    pbar = ProgressBar(; columns=details, width=80)
    with(pbar) do
        job = addjob!(pbar; N=5)
        for i in 1:5
            update!(job)
            sleep(0.02)
        end
    end
end
┌ Warning: Assignment to `pbar` in soft scope is ambiguous because a global variable by the same name exists: `pbar` will be treated as a new local. Disambiguate by using `local pbar` to suppress this warning or `global pbar` to assign to the existing global variable.
└ @ progressbars.md:146

────────────────────── progress ───────────────────────


────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━                          ────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━                          ────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━                  ────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━         ────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━




────────────────────── progress ───────────────────────


────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━                     2/5  40%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━              3/5  60%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━        4/5  80%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%




────────────────────── progress ───────────────────────


────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━                 2/5  40%  elapsed:   34ms remaining:  51.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━                 2/5  40%  elapsed:   52ms remaining:  78.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━            3/5  60%  elapsed:   71ms remaining:  47.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━       4/5  80%  elapsed:   89ms remaining:  22.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%  elapsed:  107ms remaining:   0.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%  elapsed:  126ms remaining:   0.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%  elapsed:  145ms remaining:   0.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%  elapsed:  163ms remaining:   0.0ms────────────────────── progress ───────────────────────
Running...  ━━━━━━━━━━━━━━━━━━━━━━━━━  5/5 100%  elapsed:  181ms remaining:   0.0ms


but you can also choose your own combination of columns:

import Term.Progress: CompletedColumn, SeparatorColumn, ProgressColumn, DescriptionColumn

mycols = [DescriptionColumn, CompletedColumn, SeparatorColumn, ProgressColumn]
cols_kwargs = Dict(
    :DescriptionColumn => Dict(:style=>"red bold")
)

pbar = ProgressBar(; columns=mycols, columns_kwargs=cols_kwargs, width=140)
with(pbar) do
    job = addjob!(pbar; N=5)
    for i in 1:5
        update!(job)
        sleep(0.02)
    end
end

────────────────────── progress ───────────────────────


────────────────────── progress ───────────────────────
Running... 2/5  ━━━━━━━━━━━━━━━                       ────────────────────── progress ───────────────────────
Running... 2/5  ━━━━━━━━━━━━━━━                       ────────────────────── progress ───────────────────────
Running... 3/5  ━━━━━━━━━━━━━━━━━━━━━━━               ────────────────────── progress ───────────────────────
Running... 4/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━        ────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


what's that cols_kwargs? You can use that to pass additional parameters to each columns, e.g. to set its style.

Custom columns

If there some kind of information that you want to display and Term doesn't have a column for it, just make your own!

You need two things: a column type that is a subtype of AbstractColumn and an update! method to update the column's text at each step of the progress bar. Here's a, not very useful, example of a column that displays random text:

using Random

using Term.Progress
import Term.Progress: AbstractColumn, DescriptionColumn, CompletedColumn, SeparatorColumn, ProgressColumn
import Term.Segments: Segment
import Term.Measures: Measure

struct RandomColumn <: AbstractColumn
    job::ProgressJob
    segments::Vector{Segment}
    measure::Measure
    style::String

    function RandomColumn(job::ProgressJob; style="red" )
        txt = Segment(randstring(6), style)
        return new(job, [txt], txt.measure, style)
    end
end


function Progress.update!(col::RandomColumn, color::String, args...)::String
    txt = Segment(randstring(6), col.style)
    return txt.text
end

which you can use as you would any column:

mycols = [DescriptionColumn, CompletedColumn, SeparatorColumn, ProgressColumn, RandomColumn]
cols_kwargs = Dict(
    :RandomColumn => Dict(:style=>"green bold")
)

pbar = ProgressBar(; columns=mycols, columns_kwargs=cols_kwargs, width=140)
with(pbar) do
    job = addjob!(pbar; N=5)
    for i in 1:5
        update!(job)
        sleep(0.02)
    end
end

────────────────────── progress ───────────────────────


────────────────────── progress ───────────────────────
Running... 2/5  ━━━━━━━━━━━━                    MB0BKM────────────────────── progress ───────────────────────
Running... 2/5  ━━━━━━━━━━━━                    5Nm45e────────────────────── progress ───────────────────────
Running... 3/5  ━━━━━━━━━━━━━━━━━━━             9LwfmY────────────────────── progress ───────────────────────
Running... 4/5  ━━━━━━━━━━━━━━━━━━━━━━━━━       i0KoaM────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ jfAvUf────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ w4GWb6────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ wK3OK1────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ldRhZ4────────────────────── progress ───────────────────────
Running... 5/5  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BqtjKs


done!

For each progress

Want to just wrap an iterable in a progress bar rendering? Check this out.

using Term
using Term.Progress

pbar = Term.ProgressBar()
foreachprogress(1:100, pbar,   description = "Outer    ", parallel=true) do i
    foreachprogress(1:5, pbar, description = "    Inner") do j
        sleep(rand() / 5)
    end
end

The loop above will render a progress bar for each iteration of the outer loop, and a progress bar for each iteration of the inner loop - easy.

ProgressLogging

I know that some of you will be thinking: hold on, Julia already had a perfectly functioning progress API with ProgressLogging.jl, can't we just use that? Long story short, yes you can. But Term's API gives you so much more control over what kind information to display and what it should look like. Nonetheless, many of you will want to use ProgressLogging in conjunction with Term, so we've made it possible, you just need to use Term's logger (see Logger):

using ProgressLogging
import Term: install_term_logger
install_term_logger()

@progress "outer...." for i in 1:3
    sleep(0.01)
end