Skip to content

Instantly share code, notes, and snippets.

@dgulino
Last active December 22, 2021 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dgulino/3373d75bcfea56fd42346fc0404dd30c to your computer and use it in GitHub Desktop.
Save dgulino/3373d75bcfea56fd42346fc0404dd30c to your computer and use it in GitHub Desktop.
ncurses numerical plotter
#!/usr/bin/env python
# Copyright 2021 Drew Gulino
# #This program is free software; you can redistribute it and/or modify
# # it under the terms of the GNU General Public License as published by
# # the Free Software Foundation; either version 2 of the License, or
# # (at your option) any later version.
# #
# # This program is distributed in the hope that it will be useful,
# # but WITHOUT ANY WARRANTY; without even the implied warranty of
# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# # GNU General Public License for more details.
# #
# # You should have received a copy of the GNU General Public License
# # along with this program; if not, write to the Free Software
# # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import sys
import os
import getopt
import time
import math
from getopt import GetoptError
from blessings import Terminal
example_output = '''
./generate_random.py | ./ngraph.py -p -t 500 -m 1000
10:57:50 631 ██████████████████████████████████████████████████████
10:57:50 860 ███████████████████████████████████████████████████████████████████████████
10:57:51 391 █████████████████████████████████▉
10:57:51 470 █████████████████████████████████████████▏
10:57:52 153 ████████████
10:57:52 789 ████████████████████████████████████████████████████████████████████▌
10:57:52 715 █████████████████████████████████████████████████████████████▊
0 500 1000
'''
class Ngraph:
def __init__(self, display_number, threshold, maximum,
timestamp, diff):
self.reset = os.popen('tput sgr0').read()
self.maximum = maximum
self.max_digits = 0
self.display_number = display_number
self.threshold = threshold
self.timestamp = timestamp
self.diff = diff
self.oldnum = 0
self.first_num = True
self.term= Terminal()
self.full_block = '\u2588' #full block
self.medium_shade = '\u2592'
self.whole_char = self.full_block
self.ts_characters = 0
def __del__(self):
sys.stdout.write(self.reset)
def partial_lookup(self,remainder):
code = {
1: '\u258F',
2: '\u258E',
3: '\u258D',
4: '\u258C',
5: '\u258B',
6: '\u258A',
7: '\u2589'
}
return code.get(remainder)
def graph(self, nums):
if self.maximum != 0:
digits = int(math.log10(self.maximum))
self.max_digits = digits + 1
for num in nums:
if self.diff:
new_oldnum = num
num = num - self.oldnum
self.oldnum = new_oldnum
if self.first_num:
self.first_num = False
continue
if num > self.maximum:
self.maximum = num
self.whole_char = self.medium_shade
else:
self.whole_char = self.full_block
with self.term.location(0, self.term.height - 2):
if num > 0:
digits = 0
if type(num) == float:
digits = int(math.log10(num)) + 4
if digits > self.max_digits:
self.max_digits = digits - 1
elif type(num) == int:
digits = int(math.log10(num)) + 2
if digits > self.max_digits:
self.max_digits = digits - 1
if num > self.maximum:
bold = self.term.bold
else:
bold = ''
color = self.term.normal
if self.threshold > 0:
if num >= self.threshold:
color = self.term.red
else:
color = ''
if self.timestamp:
ts = time.strftime("%H:%M:%S ")
self.ts_characters = len(ts)
print(ts,end='')
scale = float( ((self.term.width - digits - self.ts_characters) * 8)/ self.maximum)
else:
scale = float( ((self.term.width - digits) * 8 )/ self.maximum)
scaled_num = float(num * scale)
if self.display_number:
displayed_max_digits = self.max_digits
if type(num) == float:
print(bold + f"{num:>{self.max_digits}.1f} ",end='')
elif type(num) == int:
print(bold + f"{num:>{self.max_digits}d} ",end='')
else:
displayed_max_digits = -1
num_whole_blocks = math.floor(scaled_num / 8)
remainder = int(scaled_num % 8)
for b in range(0,num_whole_blocks):
print(bold + color + self.whole_char, end='') # bar chart
if remainder != 0:
partial_char = self.partial_lookup(remainder)
print(bold + color + str(partial_char), end='')
for c in range(0,self.term.width - num_whole_blocks - displayed_max_digits - self.ts_characters - 2):
print(" ",end='')
else:
for c in range(0,self.term.width - num_whole_blocks - displayed_max_digits - self.ts_characters - 1):
print(" ",end='')
else:
if self.timestamp:
ts = time.strftime("%H:%M:%S ")
print(self.term.normal + ts,end='')
if self.display_number:
sys.stdout.write(str(num))
#sys.stdout.write("\n")
#sys.stdout.flush()
print(self.term.normal,flush=True)
if self.display_number:
with self.term.location(self.ts_characters + self.max_digits + 1, self.term.height - 1):
print("0", end='')
else:
with self.term.location(self.ts_characters + 0, self.term.height - 1):
print("0", end='')
with self.term.location(self.ts_characters + int(self.term.width / 2), self.term.height - 1):
print(int(self.maximum / 2), end='')
with self.term.location(self.term.width - self.max_digits, self.term.height - 1):
print(int(self.maximum))
def usage(progname):
print("Usage: " + progname)
version()
print("[-h --help]")
print("[-v --version]")
print("[-n --no_number] Don't display number w/graph")
print("[-t --threshold=] Will color lines over this value")
print(
"[-m --maximum=] Presets the scale for this maximum value(default = 0)"
)
print(
"[-p --timestamp] Print local hour:min:sec timestamp per line (default = False)"
)
print(
"[-r --diff] subtract previous entry to track always increasing values"
)
print(
"[--numeric_type] how to display numbers: int or float (default = int)"
)
def version():
print("version: 1.0")
def main(argv, stdout, environ):
progname = argv[0]
display_number = True
threshold = 0
maximum = 0
timestamp = False
delimiter = " "
diff = False
numeric_type = int
try:
arglist, args = getopt.getopt(argv[1:], "hvnpt:m:d:", [
"help", "version", "no_number", "timestamp", "diff",
"threshold=", "maximum=", "delimiter=", "numeric_type="
])
except GetoptError:
print("Invalid Option!")
usage(progname)
return
# Parse command line arguments
for field, val in arglist:
if field in ("-h", "--help"):
usage(progname)
return
elif field in ("-v", "--version"):
version()
return
elif field in ("-n", "--number"):
display_number = False
elif field in ("-p", "--timestamp"):
timestamp = True
elif field in ("-r", "--diff"):
diff = True
elif field in ("-t", "--threshold"):
threshold = float(val)
elif field in ("-m", "--maximum"):
maximum = float(val)
elif field in ("-d", "--delimiter"):
delimiter = val
elif field in ("--numeric_type"):
if val == "int":
numeric_type = int
elif val == "float":
numeric_type = float
ngraph = Ngraph(display_number, threshold, maximum,
timestamp, diff)
main_loop(ngraph, delimiter, numeric_type=numeric_type)
def number_string(NumberString):
if NumberString.isdigit():
Number = int(NumberString)
else:
Number = float(NumberString)
return Number
def main_loop(ngraph, delimiter, numeric_type):
while 1:
try:
numbers = sys.stdin.readline().split(delimiter)
if len(numbers) < 1:
break
numbers = [numeric_type(number.strip()) for number in numbers]
except ValueError:
continue
except KeyboardInterrupt:
print("Stopped with keyboard input")
break
ngraph.graph(numbers)
if len(numbers) > 1:
sys.stdout.write("\n")
sys.stdout.flush()
if __name__ == "__main__":
main(sys.argv, sys.stdout, os.environ)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment