As a programmer, you might add a progress bar so that the user has feedback while they wait for a slow task.
If you are writing a console (CLI) application, then you need to make your progress bars from text. A good command-line progress bar should update in small increments, like this example:
This uses Unicode Block Elements to give the progress bar a higher resolution.
Code | Character |
---|---|
U+2588 | █ |
U+2589 | ▉ |
U+258A | ▊ |
U+258B | ▋ |
U+258C | ▌ |
U+258D | ▍ |
U+258E | ▎ |
U+258F | ▏ |
A lot of applications use plain ASCII in their progress bars. The progress bar in wget, for example, uses ===>
characters only, like this:
Progress bars made from ASCII characters like =
and #
signs are very common, most likely because of the historical portability issues around non-ASCII text. Nowadays, UTF-8 support is ubiquitous, and it’s pointless to adhere to such limitations.
Example: Better progress bars in python
The animation at the top of this blog post is a simple python script.
import sys
import time
from progress_bar import ProgressBar
"""
Example usage of ProgressBar class
"""
print("Doing work\n")
with ProgressBar(sys.stdout) as progress:
for i in range(0,800):
progress.update(i / 800)
time.sleep(0.05)
print("\nDone.\n");
The script progress_bars.py
, written for Python 3, contains a class that allows the progress bar to be created and drawn on different types of terminals.
import abc
import math
import shutil
import sys
import time
from typing import TextIO
"""
Produce progress bar with ANSI code output.
"""
class ProgressBar(object):
def __init__(self, target: TextIO = sys.stdout):
self._target = target
self._text_only = not self._target.isatty()
self._update_width()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if(exc_type is None):
# Set to 100% for neatness, if no exception is thrown
self.update(1.0)
if not self._text_only:
# ANSI-output should be rounded off with a newline
self._target.write('\n')
self._target.flush()
pass
def _update_width(self):
self._width, _ = shutil.get_terminal_size((80, 20))
def update(self, progress : float):
# Update width in case of resize
self._update_width()
# Progress bar itself
if self._width < 12:
# No label in excessively small terminal
percent_str = ''
progress_bar_str = ProgressBar.progress_bar_str(progress, self._width - 2)
elif self._width < 40:
# No padding at smaller size
percent_str = "{:6.2f} %".format(progress * 100)
progress_bar_str = ProgressBar.progress_bar_str(progress, self._width - 11) + ' '
else:
# Standard progress bar with padding and label
percent_str = "{:6.2f} %".format(progress * 100) + " "
progress_bar_str = " " * 5 + ProgressBar.progress_bar_str(progress, self._width - 21)
# Write output
if self._text_only:
self._target.write(progress_bar_str + percent_str + '\n')
self._target.flush()
else:
self._target.write('\033[G' + progress_bar_str + percent_str)
self._target.flush()
@staticmethod
def progress_bar_str(progress : float, width : int):
# 0 <= progress <= 1
progress = min(1, max(0, progress))
whole_width = math.floor(progress * width)
remainder_width = (progress * width) % 1
part_width = math.floor(remainder_width * 8)
part_char = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"][part_width]
if (width - whole_width - 1) < 0:
part_char = ""
line = "[" + "█" * whole_width + part_char + " " * (width - whole_width - 1) + "]"
return line
Aside from the use of box drawing characters, this script includes a few other things which a good progress bar should implement:
- Resize the progress bar when you resize the terminal
- Simplify the progress bar on very small terminals
- Don’t print ANSI terminal codes if the script is not connected to a terminal
- Round off to 100% once the use of the progress bar completes without error
After writing this, I discovered that the progress pypi package can also use these box characters, so I haven’t packaged this code up. I haven’t used progress
before, but you might like to evaluate it for your own applications.