AsciiChart.py
1 from __future__ import division 2 from math import ceil, floor, isnan 3 # Derived from asciichartpy | https://github.com/kroitor/asciichart/blob/master/asciichartpy/__init__.py 4 class AsciiChart: 5 def __init__(self, glyphset="unicode"): 6 self.symbols = ['┼', '┤', '╶', '╴', '─', '╰', '╭', '╮', '╯', '│'] 7 if glyphset == "plain": 8 self.symbols = ['+', '|', '-', '-', '-', '\'', ',', '.', '`', '|'] 9 def plot(self, series, cfg=None): 10 if len(series) == 0: 11 return '' 12 if not isinstance(series[0], list): 13 if all(isnan(n) for n in series): 14 return '' 15 else: 16 series = [series] 17 cfg = cfg or {} 18 minimum = cfg.get('min', min(filter(lambda n: not isnan(n), [j for i in series for j in i]))) 19 maximum = cfg.get('max', max(filter(lambda n: not isnan(n), [j for i in series for j in i]))) 20 symbols = cfg.get('symbols', self.symbols) 21 if minimum > maximum: 22 raise ValueError('The min value cannot exceed the max value.') 23 interval = maximum - minimum 24 offset = cfg.get('offset', 3) 25 height = cfg.get('height', interval) 26 ratio = height / interval if interval > 0 else 1 27 28 min2 = int(floor(minimum * ratio)) 29 max2 = int(ceil(maximum * ratio)) 30 31 def clamp(n): 32 return min(max(n, minimum), maximum) 33 34 def scaled(y): 35 return int(round(clamp(y) * ratio) - min2) 36 37 rows = max2 - min2 38 39 width = 0 40 for i in range(0, len(series)): 41 width = max(width, len(series[i])) 42 width += offset 43 44 placeholder = cfg.get('format', '{:8.2f} ') 45 46 result = [[' '] * width for i in range(rows + 1)] 47 48 for y in range(min2, max2 + 1): 49 if callable(placeholder): 50 label = placeholder(maximum - ((y - min2) * interval / (rows if rows else 1))).rjust(12) 51 else: 52 label = placeholder.format(maximum - ((y - min2) * interval / (rows if rows else 1))) 53 54 result[y - min2][max(offset - len(label), 0)] = label 55 result[y - min2][offset - 1] = symbols[0] if y == 0 else symbols[1] 56 57 d0 = series[0][0] 58 if not isnan(d0): 59 result[rows - scaled(d0)][offset - 1] = symbols[0] 60 61 for i in range(0, len(series)): 62 for x in range(0, len(series[i]) - 1): 63 d0 = series[i][x + 0] 64 d1 = series[i][x + 1] 65 66 if isnan(d0) and isnan(d1): 67 continue 68 69 if isnan(d0) and not isnan(d1): 70 result[rows - scaled(d1)][x + offset] = symbols[2] 71 continue 72 73 if not isnan(d0) and isnan(d1): 74 result[rows - scaled(d0)][x + offset] = symbols[3] 75 continue 76 77 y0 = scaled(d0) 78 y1 = scaled(d1) 79 if y0 == y1: 80 result[rows - y0][x + offset] = symbols[4] 81 continue 82 83 result[rows - y1][x + offset] = symbols[5] if y0 > y1 else symbols[6] 84 result[rows - y0][x + offset] = symbols[7] if y0 > y1 else symbols[8] 85 86 start = min(y0, y1) + 1 87 end = max(y0, y1) 88 for y in range(start, end): 89 result[rows - y][x + offset] = symbols[9] 90 91 return '\n'.join([''.join(row).rstrip() for row in result])