kroitor / asciichart

Nice-looking lightweight console ASCII line charts ╭┈╯ for NodeJS, browsers and terminal, no dependencies
MIT License
1.84k stars 94 forks source link

New features #3

Open arkihillel opened 6 years ago

arkihillel commented 6 years ago

I like this simple, easy to use package that I use for node statistic clis. I'm missing some nice to have options though:

Multiple series Ability to see more than one series on the same chart

Colors Ability to affect colors to lines. npm's colors may be used there

Max width Some charts can be way larger than a terminal. Detecting the terminal length and sub-setting the series sent may be a good way to tackle this issue

kroitor commented 6 years ago

Hi, @arkihillel !

Thanks for your feedback!

Multiple series Colors

These two points I'm going to do hopefully soon (need a few days free of other tasks to do it). Also, I highly recommend you to take a look at these awesome packages by @xpl:

Max width

This cannot be done robustly for all terminals, so it's a little beyond the scope of this small library (I'm trying to keep it simple). But I did use the ansicolor and blessed together with asciichart to render a dashboard and a chart inside it, like this one:

preview

You can use any curses-like library to do the same. Alignment, center, full-width, full-height, etc... So blessed takes care of that, and can crop or scroll the "inside" content according to your rules. It also detects resizes, mouse events and much more. Hope this answers your questions.

joergd commented 6 years ago

Yes - multiple series (with different colours) would be tops!!

theBliz commented 4 years ago

@kroitor I'm using this package (awesome, thank you!) and would like to try having 2 lines in the same chart. I ended up looking at the packages you suggested but didn't understand how to use them for this purpose. Can you elaborate, please?

kroitor commented 4 years ago

@theBliz it's a bit tricky, since drawing two lines requires changing the algorithm a bit, to find proper min/max values. After that you can plot each line separately using the same min/max range and then merge the two chart layers one over another skipping the whitespaces for top layer "opacity". I hope to add that functionality to the lib soon.

el3ment commented 4 years ago

+1 vote for the multi-line chart! Adding color would also be pretty neat.

crbyxwpzfl commented 9 months ago

just hid this since I saw a better issue to leave this https://github.com/kroitor/asciichart/issues/57#issuecomment-1766948289

hi just wanted to stop to say this plotter is really really cool!! here is a little example for what i use your plotter for.

I try to use it as a live serial plotter for sensor data on a rp2040. To scroll the plot I a.pop(0) for any value above a certain len(a) and wipe the lines of the previous plot.

# wipe lines
for i in range(-1,len(result)):  # range from -1 to scroll plot to the top or 0 to keep plot at position
    print('\033[1A', end='\x1b[2K')

this is really usable for smaller scales

https://github.com/kroitor/asciichart/assets/60987359/1d4810c5-469b-49f2-8279-b221595d3743

full python ### full python ```py from __future__ import division from math import ceil, floor, isnan black = "\033[30m" red = "\033[31m" green = "\033[32m" yellow = "\033[33m" blue = "\033[34m" magenta = "\033[35m" cyan = "\033[36m" lightgray = "\033[37m" default = "\033[39m" darkgray = "\033[90m" lightred = "\033[91m" lightgreen = "\033[92m" lightyellow = "\033[93m" lightblue = "\033[94m" lightmagenta = "\033[95m" lightcyan = "\033[96m" white = "\033[97m" reset = "\033[0m" __all__ = [ 'plot', 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightgray', 'default', 'darkgray', 'lightred', 'lightgreen', 'lightyellow', 'lightblue', 'lightmagenta', 'lightcyan', 'white', 'reset', ] # Python 3.2 has math.isfinite, which could have been used, but to support older # versions, this little helper is shorter than having to keep doing not isnan(), # plus the double-negative of "not is not a number" is confusing, so this should # help with readability. def _isnum(n): return not isnan(n) def colored(char, color): if not color: return char else: return color + char + reset def plot(series, cfg=None): if len(series) == 0: return '' if not isinstance(series[0], list): if all(isnan(n) for n in series): return '' else: series = [series] cfg = cfg or {} colors = cfg.get('colors', [None]) minimum = cfg.get('min', min(filter(_isnum, [j for i in series for j in i]))) maximum = cfg.get('max', max(filter(_isnum, [j for i in series for j in i]))) default_symbols = ['┼', '┤', '╶', '╴', '─', '╰', '╭', '╮', '╯', '│'] symbols = cfg.get('symbols', default_symbols) if minimum > maximum: raise ValueError('The min value cannot exceed the max value.') interval = maximum - minimum offset = cfg.get('offset', 3) height = cfg.get('height', interval) ratio = height / interval if interval > 0 else 1 min2 = int(floor(minimum * ratio)) max2 = int(ceil(maximum * ratio)) def clamp(n): return min(max(n, minimum), maximum) def scaled(y): return int(round(clamp(y) * ratio) - min2) rows = max2 - min2 width = 0 for i in range(0, len(series)): width = max(width, len(series[i])) width += offset placeholder = cfg.get('format', '{:8.2f} ') result = [[' '] * width for i in range(rows + 1)] # axis and labels for y in range(min2, max2 + 1): label = placeholder.format(maximum - ((y - min2) * interval / (rows if rows else 1))) result[y - min2][max(offset - len(label), 0)] = label result[y - min2][offset - 1] = symbols[0] if y == 0 else symbols[1] # zero tick mark # first value is a tick mark across the y-axis d0 = series[0][0] if _isnum(d0): result[rows - scaled(d0)][offset - 1] = symbols[0] for i in range(0, len(series)): color = colors[i % len(colors)] # plot the line for x in range(0, len(series[i]) - 1): d0 = series[i][x + 0] d1 = series[i][x + 1] if isnan(d0) and isnan(d1): continue if isnan(d0) and _isnum(d1): result[rows - scaled(d1)][x + offset] = colored(symbols[2], color) continue if _isnum(d0) and isnan(d1): result[rows - scaled(d0)][x + offset] = colored(symbols[3], color) continue y0 = scaled(d0) y1 = scaled(d1) if y0 == y1: result[rows - y0][x + offset] = colored(symbols[4], color) continue result[rows - y1][x + offset] = colored(symbols[5], color) if y0 > y1 else colored(symbols[6], color) result[rows - y0][x + offset] = colored(symbols[7], color) if y0 > y1 else colored(symbols[8], color) start = min(y0, y1) + 1 end = max(y0, y1) for y in range(start, end): result[rows - y][x + offset] = colored(symbols[9], color) #return '\n'.join([''.join(row).rstrip() for row in result]) print(f"{chr(10).join([''.join(row).rstrip() for row in result])}") # whipe lines for i in range(-1,len(result)): # plus -1 to scroll plot to the top or 0 to keep plot at position print('\033[1A', end='\x1b[2K') from math import cos from math import sin from math import pi import time x = []; a = []; b = []; c = [] i = 1 width = 90 while True: time.sleep(0.05) a.append(7 * round(sin(i * ((pi * 4) / width)), 2)) b.append(7 * round(cos(i * ((pi * 4) / width)), 2)) c.append(7 * round(-sin(i * ((pi * 4) / width)), 2)) i += 1 if len(a) >= 200: # x aspect a.pop(0); b.pop(0); c.pop(0) plot([a, b, c], {'min': -8, 'max': 8, 'height': 30, 'format': '{:8.0f}', 'colors': [blue, lightcyan, lightmagenta]} ) ```
but for bigger scales it starts blinking. this is about the limit for me ### 1080/300aspect https://github.com/kroitor/asciichart/assets/60987359/88b0ce81-f077-4f9a-8ff1-47bfcf79a1f0

your implementation with blessed is way nicer though

...

You can use any curses-like library to do the same. Alignment, center, full-width, full-height, etc... So blessed takes care of that, and can crop or scroll the "inside" content according to your rules. It also detects resizes, mouse events and much more. Hope this answers your questions.

Originally posted by @kroitor in https://github.com/kroitor/asciichart/issues/3#issuecomment-338340713

anyways just thought I leave this here as a simple way for live plots or scrolling plots. perhaps someone has tips to make this better