#!/usr/bin/python3 # -*- coding: utf-8 -*- """Plot horizontally-stacked bars with Unicode and ANSI escapes. Example usage: ./barstack.py 38 7 22.1 This will plot three bars with proportional sizes 38, 7, and 22.1, one next to the other, which together fill the horizontal width of the screen. For a good time, call x=950; y=0; for i in {1..512}; do ./barstack.py "$((1000+$x))" "$((1000-$x))" "$((1000+$y))" "$((1000-$y))" : $((y-=((x+=($y>>5))>>5))) done I’m thinking maybe to add palettes, 24-bit colors, textual legends, appealing contrasting text colors, and so on. In old versions of gnome-terminal and Konsole, the characters in question don’t display correctly (there’s some space left above them in the character set, resulting in incorrect pixels). In Termux there is a spurious space both above and below the block character. xterm and recent versions of gnome-terminal and Konsole seem to work reasonably well. """ from __future__ import division, print_function import argparse, os # Left eighth blocks, in reverse order, followed by a space. U+2590 # and up, except for the space. blocks = '▉▊▋▌▍▎▏ ' def parser(): formatter = argparse.RawDescriptionHelpFormatter p = argparse.ArgumentParser(description=__doc__, formatter_class=formatter) p.add_argument('-n', '--no-legend', action='store_true', help='Omit textual legends within bars') p.add_argument('-w', '--width', type=int, default=guess_terminal_width(), help='The terminal width to target (default %(default)s' + ' on this tty)') p.add_argument('q', nargs='+', help='The numbers to plot') return p def guess_terminal_width(): termsize = os.popen('stty size').read().split() if not termsize or int(termsize[1]) == 0: return 80 else: return int(termsize[1]) class InvocationError(Exception): pass def plot_absolute(positions, legends): pos = 0 output = [] for color, (desired, legend) in enumerate(zip(positions, legends)): assert desired >= 0, desired assert desired >= pos, (desired, pos) output.append('\033[3%d;4%dm' % ((color - 1) % 8, color % 8)) if pos % 8: # advance to character cell boundary jank = 8 - pos % 8 output.append(blocks[jank]) pos += jank w = max((desired - pos) // 8, 0) label = str(legend) output.append(label.center(w) if len(label) <= w else ' ' * w) pos = desired output.append('\033[0m') # reset return ''.join(output) def plot(args): ss = [0] # sums for qi in args.q: try: qi = int(qi) except ValueError: qi = float(qi) if qi < 0: raise InvocationError("bar size must be nonnegative", qi) ss.append(ss[-1] + qi) width = 8 * args.width return plot_absolute([int(round(qsi * width / ss[-1])) for qsi in ss[1:]], [''] * len(args.q) if args.no_legend else args.q) def main(): print(plot(parser().parse_args())) if __name__ == '__main__': main()