diff --git a/.gitignore b/.gitignore index ab47253..fa5025c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ __pycache__ -*.pyc \ No newline at end of file +*.pyc +*.log + diff --git a/README.md b/README.md new file mode 100644 index 0000000..783849a --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Graphing + +![example graph](graph.png) + +A cli graphing tool to plot anything you want diff --git a/cpu-temp.png b/cpu-temp.png new file mode 100644 index 0000000..057d075 Binary files /dev/null and b/cpu-temp.png differ diff --git a/default.nix b/default.nix index e098d41..c8f70aa 100644 --- a/default.nix +++ b/default.nix @@ -6,7 +6,7 @@ stdenv.mkDerivation rec { buildInputs = [ (python3.withPackages (ps: with ps; [ pylint - plotly + matplotlib clint ])) ]; diff --git a/graph.png b/graph.png new file mode 100644 index 0000000..59bb0f7 Binary files /dev/null and b/graph.png differ diff --git a/graphing-nix.sh b/graphing-nix.sh old mode 100644 new mode 100755 index e69de29..b3aff78 --- a/graphing-nix.sh +++ b/graphing-nix.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i bash -p python3 python37Packages.matplotlib + +$(dirname $(realpath $0))/graphing.py "$@" \ No newline at end of file diff --git a/graphing.py b/graphing.py old mode 100644 new mode 100755 index e69de29..8a49f19 --- a/graphing.py +++ b/graphing.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import time +import argparse +import matplotlib +from graphing import * + +# matplotlib setup +matplotlib.pyplot.style.use('dark_background') +matplotlib.rcParams['toolbar'] = 'None' + +# arg parser +parser = argparse.ArgumentParser(description='Visualize data as graphs') +parser.add_argument('title', type=str, help="name of the chart", default=None, nargs='?') +parser.add_argument('-n', type=int, help="number of records to show, -1 for no limit. Default 50", default=50, dest="samples") +parser.add_argument('-l', '--limits', type=str, help="limits for the y-axis. If not set, use best-fit. Example values are 0-10, 5- (starting at 5), or -10 (lower than 10)") +parser.add_argument('-b', '--batch', help="batch processing. Do not use timestamp from when they were read from stdin.", action="store_true") +parser.add_argument('-i', '--input', type=str, help="read from file rather than stdin. Also enables batch mode and sets samples to -1.") +parser.add_argument('-o', '--output', type=str, help="Write final chart to file") +parser.add_argument('-f', '--full', help="fullscreen mode - hide all axis labels and title, only display graph.", action="store_true") +parser.add_argument('-x', '--read-x-values', help="read value of x-axis from input. Input is now \", \" tuple (comma separated)", action="store_true", dest="read_x") + +args = parser.parse_args() + +# setup batch and sample count for when using input file +if args.input: + args.batch = True + args.samples = -1 + +# parse limit arg +args.limits = parse_limits(args.limits) + +# instatiate chart with our args +chart = Chart(title=args.title, limits=args.limits, batch=args.batch, samples=args.samples, fullscreen=args.full, show=not args.output, read_x=args.read_x) + + +# final waiting state +try: + if not args.input: + # read args from stdin + while_stdin(chart.read_input) + else: + with open(args.input) as f: + for line in f: + chart.read_input(line.strip()) + + chart.ui_update(final_draw = True) + while not args.output: + time.sleep(1) +except KeyboardInterrupt: + if args.output: + chart.save_to_file(args.output) + else: + chart.ui_update(final_draw = True) \ No newline at end of file diff --git a/graphing/__init__.py b/graphing/__init__.py new file mode 100644 index 0000000..0f407d6 --- /dev/null +++ b/graphing/__init__.py @@ -0,0 +1,3 @@ +from .stdin import while_stdin +from .chart import Chart +from .helpers import parse_limits \ No newline at end of file diff --git a/graphing/bar.py b/graphing/bar.py new file mode 100644 index 0000000..84904c2 --- /dev/null +++ b/graphing/bar.py @@ -0,0 +1,7 @@ +from .chart import Chart + + +class BarChart(Chart): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.draw_opts = "b." \ No newline at end of file diff --git a/graphing/chart.py b/graphing/chart.py new file mode 100644 index 0000000..f917f59 --- /dev/null +++ b/graphing/chart.py @@ -0,0 +1,87 @@ +from datetime import datetime, timedelta +import matplotlib +import matplotlib.pyplot as plt +import matplotlib.dates as mdates + +class Chart: + def __init__(self, samples = 50, title = None, limits = None, batch=False, show=True, fullscreen=False, read_x=False): + self.start = datetime.now() + self.x = [] + self.y = [] + self.samples = samples + self.fig = plt.figure() + self.axis = self.fig.add_subplot(111, autoscale_on = True, xticklabels = []) + + scale_axis = "both" if not limits else "x" + self.axis.autoscale(enable=True, axis=scale_axis, tight = 2) + # save limits + self.limits = limits + self.title = title + + self.axis.set_label(title) + self.fig.canvas.draw() + self.draw_opts = 'r-' + self.line = False + + # upate timeout code (limit to 10fps) + self.last_update = datetime.min + self.update_timeout = timedelta(seconds=0.1) + + self.read_x = read_x + + # if batch mode, do not use timestamp when read from stdin + self.batch = batch + self.fullscreen = fullscreen + + if show: + plt.show(block=False) + + def ui_update(self, final_draw=False): + if len(self.y) < 2: + return + + if not final_draw and datetime.now() - self.last_update < self.update_timeout: + return + + self.axis.clear() + # call plot method that is easily overrideable + self.plot() + + if self.limits: + self.axis.set_ylim(self.limits) + + if not self.batch: + self.axis.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S")) + self.fig.autofmt_xdate() + + + if self.fullscreen: + plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0) + else: + plt.title(self.title) + + self.fig.canvas.draw() + self.fig.canvas.flush_events() + self.last_update = datetime.now() + + + def plot(self): + + if self.batch: + self.axis.plot(self.y, self.draw_opts) + else: + self.axis.plot_date(self.x, self.y, self.draw_opts) + + def read_input(self, input): + self.y.append(float(input)) + self.x.append(datetime.now()) + + if self.samples > 0 and len(self.y) > self.samples: + self.y.pop(0) + self.x.pop(0) + + self.ui_update() + + def save_to_file(self, file): + self.ui_update(final_draw = True) + plt.savefig(file) \ No newline at end of file diff --git a/graphing/helpers.py b/graphing/helpers.py new file mode 100644 index 0000000..bfbd90e --- /dev/null +++ b/graphing/helpers.py @@ -0,0 +1,22 @@ +def parse_limits(limits): + if not limits: + return [None, None] + + limits = limits.strip() + + if limits[0] == '-': + return [None, int(limits[1:])] + elif limits[-1] == '-': + return [int(limits[:-1]), None] + + limits = limits.split("-") + + if len(limits) != 2: + print("unknown limits: " + str(limits) + " - using no limits") + return [None, None] + + try: + return [int(x) for x in limits] + except ValueError as exp: + print(exp + " - using no limits") + return [None, None] \ No newline at end of file diff --git a/graphing/stdin.py b/graphing/stdin.py new file mode 100644 index 0000000..f9699e9 --- /dev/null +++ b/graphing/stdin.py @@ -0,0 +1,8 @@ +import sys + +def while_stdin(func): + while True: + line = sys.stdin.readline() + if line is "": + break + func(line[:-1]) diff --git a/temps.log.bkuo b/temps.log.bkuo new file mode 100644 index 0000000..50741c7 --- /dev/null +++ b/temps.log.bkuo @@ -0,0 +1,169 @@ +54.625 +54.25 +53.75 +53.25 +53 +52.5 +52.125 +51.875 +51.5 +51 +50.75 +50.375 +50 +49.625 +49.25 +67.75 +77.5 +78.5 +79.5 +80.375 +81.375 +82.25 +82.75 +83 +83.5 +83.75 +84 +84 +84 +84.375 +84.75 +84.75 +84.875 +84.875 +85 +85.25 +85.5 +85.5 +85.75 +85.75 +86 +86.25 +86.5 +86.875 +87 +87.25 +87.375 +87.75 +88.125 +88.5 +88.5 +88.5 +88.5 +88.5 +88.625 +88.75 +89 +89.125 +89.25 +89.25 +89.5 +89.75 +89.875 +90 +90 +90.125 +90.375 +90.5 +90.625 +90.625 +90.75 +90.875 +91 +91.125 +91.375 +91.5 +91.5 +91.5 +91.75 +91.75 +92 +92 +92 +92.125 +92.25 +92.25 +92.25 +92.375 +92.5 +92.5 +92.5 +92.5 +92.5 +92.375 +91.875 +91.25 +90.75 +90.125 +89.625 +89 +88.5 +87.875 +87.375 +86.875 +86.25 +85.75 +85.125 +84.625 +84 +83.5 +82.875 +82.375 +81.75 +81.25 +80.625 +80.125 +79.5 +79 +78.375 +77.875 +77.25 +76.75 +76.25 +75.625 +75.125 +74.5 +74 +73.375 +72.875 +72.25 +71.75 +71.125 +70.625 +70 +69.5 +68.875 +68.375 +67.75 +67.25 +66.75 +66.25 +65.75 +65.25 +64.75 +64.25 +63.875 +63.375 +62.875 +62.5 +62 +61.5 +61.125 +60.625 +60.125 +59.75 +59.25 +58.875 +58.5 +58.125 +66.75 +66.25 +65.625 +65.25 +64.625 +64.125 +63.625 +63.125 +62.625 +62.125