initial commit
parent
f882674c70
commit
1d1190913f
@ -1,2 +1,4 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.log
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
# Graphing
|
||||
|
||||
![example graph](graph.png)
|
||||
|
||||
A cli graphing tool to plot anything you want
|
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#! nix-shell -i bash -p python3 python37Packages.matplotlib
|
||||
|
||||
$(dirname $(realpath $0))/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 \"<x>, <y>\" 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)
|
@ -0,0 +1,3 @@
|
||||
from .stdin import while_stdin
|
||||
from .chart import Chart
|
||||
from .helpers import parse_limits
|
@ -0,0 +1,7 @@
|
||||
from .chart import Chart
|
||||
|
||||
|
||||
class BarChart(Chart):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.draw_opts = "b."
|
@ -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)
|
@ -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]
|
@ -0,0 +1,8 @@
|
||||
import sys
|
||||
|
||||
def while_stdin(func):
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if line is "":
|
||||
break
|
||||
func(line[:-1])
|
@ -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
|
Loading…
Reference in New Issue