Table-and-Graph-Libs/python/graphs.py
2022-02-01 07:20:23 -08:00

459 lines
14 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Teal Dulcet, CS546
from __future__ import division, print_function, unicode_literals
import sys
import math
import shutil
import textwrap
from wcwidth import wcswidth
from typing import List, Tuple, Optional, Sequence, Callable
import locale
locale.setlocale(locale.LC_ALL, '')
styles = [
["-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"], # ASCII
["", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"], # Basic
["", "", "", "", "", "", "", "", "", "", ""], # Light
["", "", "", "", "", "", "", "", "", "", ""], # Heavy
["", "", "", "", "", "", "", "", "", "", ""], # Double
["", "", "", "", "", "", "", "", "", "", ""], # Light Dashed
["", "", "", "", "", "", "", "", "", "", ""] # Heavy Dashed
]
# [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]] #No border
colors = ["\033[39m", "\033[30m", "\033[31m", "\033[32m", "\033[33m", "\033[34m", "\033[35m", "\033[36m",
"\033[37m", "\033[90m", "\033[91m", "\033[92m", "\033[93m", "\033[94m", "\033[95m", "\033[96m", "\033[97m"]
dots = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]
values = [[0x1, 0x2, 0x4, 0x40], [0x8, 0x10, 0x20, 0x80]]
fractions = {
"¼": 1.0 / 4.0,
"½": 1.0 / 2.0,
"¾": 3.0 / 4.0,
"": 1.0 / 7.0,
"": 1.0 / 9.0,
"": 1.0 / 10.0,
"": 1.0 / 3.0,
"": 2.0 / 3.0,
"": 1.0 / 5.0,
"": 2.0 / 5.0,
"": 3.0 / 5.0,
"": 4.0 / 5.0,
"": 1.0 / 6.0,
"": 5.0 / 6.0,
"": 1.0 / 8.0,
"": 3.0 / 8.0,
"": 5.0 / 8.0,
"": 7.0 / 8.0
}
constants = {
"π": math.pi,
"e": math.e
}
def strcol(str: str) -> int:
"""Returns the number of columns that the given string would take up if printed."""
width = wcswidth(str)
if width == -1:
print("\nError! wcswidth failed. Nonprintable wide character.", file=sys.stderr)
sys.exit(1)
return width
# return len(str)
def outputlabel(label: float) -> Tuple[int, str]:
"""Outputs a label in a nice, human readable format."""
"""Convert fractions and constants to Unicode characters"""
output = False
fractionpart, intpart = math.modf(label)
fractionpart = abs(fractionpart)
strm = ""
for fraction in fractions:
if abs(fractionpart - fractions[fraction]) < sys.float_info.epsilon:
if intpart != 0:
strm += str(intpart)
strm += fraction
output = True
break
if abs(label) >= sys.float_info.epsilon and not output:
for constant in constants:
if not output and label % constants[constant] == 0:
intpart = label / constants[constant]
if intpart == -1:
strm += "-"
elif intpart != 1:
strm += str(intpart)
strm += constant
output = True
break
if not output:
strm += "{0:n}".format(label)
length = strcol(strm)
return length, strm
def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, array: List[List[int]], border: bool=True, axislabel: bool=True, axisunitslabel: bool=True, style: int=2, title: Optional[str]=None) -> int:
"""Output graph"""
if not array:
return 1
if not (0 >= style > len(styles)):
return 1
if height == 0:
return 1
if width == 0:
return 1
w = shutil.get_terminal_size()
aheight = height // 4
awidth = width // 2
if aheight > w.lines:
print("The height of the graph ({0}) is greater then the height of the terminal ({1}).".format(
aheight, w.lines), file=sys.stderr)
return 1
if awidth > w.columns:
print("The width of the graph ({0}) is greater then the width of the terminal ({1}).".format(
awidth, w.columns), file=sys.stderr)
return 1
if xmin >= xmax:
print("xmin must be less than xmax.", file=sys.stderr)
return 1
if ymin >= ymax:
print("ymin must be less than ymax.", file=sys.stderr)
return 1
xscl = width / (xmax - xmin)
yscl = height / (ymax - ymin)
xaxis = width - (xmax * xscl)
yaxis = ymax * yscl
divisor = 2 * 4 * ((width // 160) + 1 if (width / 160.0) > 1 else 1)
if title:
print(textwrap.fill(title, width=w.columns))
strm = ""
i = 0
while i < height:
ayaxis = i <= yaxis and (i + 4) > yaxis
yaxislabel = i <= (yaxis + 4) and (i + 4) > (yaxis + 4)
ylabelstrm = ""
ylabellength = 0
if border and axislabel and axisunitslabel:
output = False
label = 0.0
adivisor = -divisor if i < yaxis else divisor
k = yaxis + adivisor
while ((i < yaxis and k >= i) or (i > yaxis and k < (i + 4))) and (i >= 4 or not axislabel) and not output:
if (i <= k and (i + 4) > k):
label = ymax - (k / yscl)
output = True
k += adivisor
if (output):
ylabellength, ylabelstrm = outputlabel(label)
ylabellength *= 2
j = 0
while j < width:
axaxis = j <= xaxis and (j + 2) > xaxis
xaxislabel = j <= (xaxis - 2) and (j + 2) > (xaxis - 2)
output = False
if border:
if axaxis and ayaxis:
strm += styles[style][6]
output = True
elif axaxis:
if axislabel and axisunitslabel:
adivisor = -divisor if i < yaxis else divisor
k = yaxis + adivisor
while ((i < yaxis and k >= i) or (i > yaxis and k < (i + 4))) and (i >= 4 or not axislabel) and not output:
if i <= k and (i + 4) > k:
strm += styles[style][7]
output = True
k += adivisor
if not output:
if i == 0:
strm += styles[style][4]
elif i >= (height - 4):
strm += styles[style][10]
else:
strm += styles[style][1]
output = True
elif ayaxis:
if axislabel and axisunitslabel:
adivisor = -divisor if j < xaxis else divisor
k = xaxis + adivisor
while ((j < xaxis and k >= j) or (j > xaxis and k < (j + 2))) and (j < (width - 4) or not axislabel) and not output:
if j <= k and (j + 2) > k:
strm += styles[style][3]
output = True
k += adivisor
if not output:
if j == 0:
strm += styles[style][2]
elif j >= (width - 2):
strm += styles[style][4]
else:
strm += styles[style][0]
output = True
elif yaxislabel and xaxislabel and axislabel and axisunitslabel:
strm += "0"
output = True
elif j >= (width - 2) and yaxislabel and axislabel:
strm += "x"
output = True
elif yaxislabel and axislabel and axisunitslabel:
label = 0.0
adivisor = -divisor if j < xaxis else divisor
if j < xaxis:
j += 2
k = xaxis + adivisor
while ((j < xaxis and k >= j) or (j > xaxis and k < (j + 2))) and j < (width - 2) and not output:
if j <= k and (j + 2) > k:
label = (k / xscl) + xmin
output = True
k += adivisor
if adivisor < 0:
j -= 2
if output:
output = False
length, astrm = outputlabel(label)
length *= 2
if (j >= xaxis or (j + length) < (xaxis - 4)) and (j + length) < (width - 2):
strm += astrm
if length > 2:
j += length - 2
if adivisor < 0:
output = True
else:
j += 2
elif i == 0 and xaxislabel and axislabel:
strm += "y"
output = True
elif (j <= (xaxis - ylabellength) and (j + 2) > (xaxis - ylabellength)) and axislabel and axisunitslabel:
strm += ylabelstrm
output = True
if ylabellength > 2:
j += ylabellength - 2
if not output:
dot = 0
color = 0
for k in range(min(2, width - j)):
for l in range(min(4, height - i)):
dot += (1 if array[j + k][i + l] else 0) * values[k][l]
if color:
if array[j + k][i + l] and color != array[j + k][i + l]:
color = 1
else:
color = array[j + k][i + l]
if color:
color -= 1
if color:
strm += colors[color]
strm += "\033[1m" + dots[dot] + "\033[22m"
if color:
strm += colors[0]
j += 2
strm += "\n"
i += 4
print(strm)
return 0
def arrays(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarrays: Sequence[Sequence[Sequence[float]]], border: bool=True, axislabel: bool=True, axisunitslabel: bool=True, style: int=2, color: int=2, title: Optional[str]=None) -> int:
"""Convert one or more arrays to graph and output"""
if not aarrays:
return 1
if not all(len(x) == 2 for aarray in aarrays for x in aarray):
print("Error: The arrays must have two columns.")
return 1
if color < 0 or color >= len(colors):
return 1
w = shutil.get_terminal_size()
aheight = height // 4
awidth = width // 2
if aheight > w.lines:
print("The height of the graph ({0}) is greater then the height of the terminal ({1}).".format(
aheight, w.lines), file=sys.stderr)
return 1
if awidth > w.columns:
print("The width of the graph ({0}) is greater then the width of the terminal ({1}).".format(
awidth, w.columns), file=sys.stderr)
return 1
if xmin == 0 and xmax == 0:
xmin = min(i[0] for aarray in aarrays for i in aarray)
xmax = max(i[0] for aarray in aarrays for i in aarray)
if ymin == 0 and ymax == 0:
ymin = min(i[1] for aarray in aarrays for i in aarray)
ymax = max(i[1] for aarray in aarrays for i in aarray)
if xmin >= xmax:
print("xmin must be less than xmax.", file=sys.stderr)
return 1
if ymin >= ymax:
print("ymin must be less than ymax.", file=sys.stderr)
return 1
xscl = width / (xmax - xmin)
yscl = height / (ymax - ymin)
xaxis = width - (xmax * xscl)
yaxis = ymax * yscl
aaarray = [[0 for j in range(height)] for i in range(width)]
for j, aarray in enumerate(aarrays):
acolor = color + 1 if len(aarrays) == 1 else (j % (len(colors) - 2)) + 3
for i in aarray:
if i[0] >= xmin and i[0] < xmax and i[1] >= ymin and i[1] < ymax:
x = int((i[0] * xscl) + xaxis)
y = int((yaxis - (i[1] * yscl)) - 1)
if aaarray[x][y]:
if aaarray[x][y] != acolor:
aaarray[x][y] = 1
else:
aaarray[x][y] = acolor
return graph(height, width, xmin, xmax, ymin, ymax, aaarray, border=border, axislabel=axislabel, axisunitslabel=axisunitslabel, style=style, title=title)
def array(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarray: Sequence[Sequence[float]], border: bool=True, axislabel: bool=True, axisunitslabel: bool=True, style: int=2, color: int=2, title: Optional[str]=None) -> int:
"""Convert single array to graph and output"""
return arrays(height, width, xmin, xmax, ymin, ymax, [aarray], border=border, axislabel=axislabel, axisunitslabel=axisunitslabel, style=style, color=color, title=title)
def functions(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, afunctions: Sequence[Callable[[float], float]], border: bool=True, axislabel: bool=True, axisunitslabel: bool=True, style: int=2, color: int=2, title: Optional[str]=None) -> int:
"""Convert one or more functions to graph and output"""
if color < 0 or color >= len(colors):
return 1
if len(afunctions) == 0:
return 1
w = shutil.get_terminal_size()
if height == 0:
height = w.lines * 4
if width == 0:
width = w.columns * 2
aheight = height / 4
awidth = width / 2
if aheight > w.lines:
print("The height of the graph ({0}) is greater then the height of the terminal ({1}).".format(
aheight, w.lines), file=sys.stderr)
return 1
if awidth > w.columns:
print("The width of the graph ({0}) is greater then the width of the terminal ({1}).".format(
awidth, w.columns), file=sys.stderr)
return 1
if xmin >= xmax:
print("xmin must be less than xmax.", file=sys.stderr)
return 1
if ymin >= ymax:
print("ymin must be less than ymax.", file=sys.stderr)
return 1
rows = width
xscl = width / (xmax - xmin)
yscl = height / (ymax - ymin)
xaxis = width - (xmax * xscl)
yaxis = ymax * yscl
array = [[0 for j in range(height)] for i in range(width)]
for j, function in enumerate(afunctions):
acolor = color + 1 if len(afunctions) == 1 else (j %
(len(colors) - 2)) + 3
for i in (x / 2 for x in range(rows * 2)):
x = (i / xscl) + xmin
y = function(x)
if x >= xmin and x < xmax and y >= ymin and y < ymax:
ax = int((x * xscl) + xaxis)
ay = int((yaxis - (y * yscl)) - 1)
if array[ax][ay]:
if array[ax][ay] != acolor:
array[ax][ay] = 1
else:
array[ax][ay] = acolor
return graph(height, width, xmin, xmax, ymin, ymax, array, border=border, axislabel=axislabel, axisunitslabel=axisunitslabel, style=style, title=title)
def function(height, width, xmin: float, xmax: float, ymin: float, ymax: float, afunction: Callable[[float], float], border: bool=True, axislabel: bool=True, axisunitslabel: bool=True, style: int=2, color: int=2, title: Optional[str]=None) -> int:
"""Convert single function to function array and output"""
return functions(height, width, xmin, xmax, ymin, ymax, [afunction], border=border, axislabel=axislabel, axisunitslabel=axisunitslabel, style=style, color=color, title=title)