/ nomadnet / vendor / AsciiChart.py
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])