From 36f2f057d86074112ae5b6462607d5791cf33c86 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Tue, 1 Feb 2022 07:20:23 -0800 Subject: [PATCH] Added Python port. --- README.md | 12 +- python/README.md | 351 ++++++++++++++++++++++++++++++++++++ python/graphs.py | 458 +++++++++++++++++++++++++++++++++++++++++++++++ python/tables.py | 263 +++++++++++++++++++++++++++ python/test.py | 111 ++++++++++++ tables.cpp | 56 +----- tables.hpp | 5 +- 7 files changed, 1195 insertions(+), 61 deletions(-) create mode 100644 python/README.md create mode 100644 python/graphs.py create mode 100644 python/tables.py create mode 100644 python/test.py diff --git a/README.md b/README.md index 6a4f928..476ee48 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Tables and Graphs -C++ Console Table and Graph/Plot Libraries +Console Table and Graph/Plot Libraries Copyright © 2018 Teal Dulcet @@ -526,16 +526,22 @@ When graphing multiple functions, colors `2` - `14` are used inorder. Color `0` Pull requests welcome! Ideas for contributions: +Both: * Add more options * Add an option to print a border around graphs/plots * Add options to word wrap and truncate long text in table cells + * Add option to center text in table cells * Add more examples * Improve the performance -* Handle newlines, tabs and formatted text in the tables +* Handle newlines and tabs in the tables * Handle formatted text in the table and graph/plot titles * Support more graph/plot colors + * Support 24-bit color * Support combining colors when functions cross -* Support plotting multiple arrays of different sizes * Update the `-t, --table` options of column command from [util-linux](https://en.wikipedia.org/wiki/Util-linux) to use the Table library * Create a new CLI tool that uses the Graph library * Port to other languages (C, Java, Rust, etc.) + +C++: +* Handle formatted text in the tables +* Support plotting multiple arrays of different sizes diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..9a4278b --- /dev/null +++ b/python/README.md @@ -0,0 +1,351 @@ +## Tables + +### Usage + +Requires Python 3.5 or greater and the [wcwidth library](https://pypi.org/project/wcwidth/), which users can install with: `pip3 install wcwidth`. + +Complete versions of all of the examples below and more can be found in the [test.py](test.py) file. + +Run with: `python3 test.py`. + +#### Output char array as table + +```py +import tables + +# Set array + +tables.array(array, None, None, headerrow=True, headercolumn=True) +``` + +Table cells can contain [Unicode characters](https://en.wikipedia.org/wiki/List_of_Unicode_characters), but not newlines and tabs. + +![](../images/char%20array%20to%20table.png) + +#### Output array as table with separate header row and column + +```py +import tables + +headerrow = ["Header row/column 1", "Header row 2", "Header row 3", "Header row 4", "Header row 5"] +headercolumn = ["Header column 2", "Header column 3", "Header column 4", "Header column 5"] + +# Set array + +tables.array(array, headerrow, headercolumn, headerrow=True, headercolumn=True) +``` + +Output same as example above. + +#### Output array as table + +```py +import tables + +# Set array + +tables.array(array, None, None) +``` + +![](../images/array%20to%20table.png) + +#### Output sorted array as table + +```py +import tables + +# Set array + +sortdimension = 0 # Column to sort by + +array = sorted(array, key=lambda x: x[sortdimension]) + +tables.array(array, None, None) +``` + +![](../images/sorted%20array%20to%20table.png) + +#### Output single function as table + +```py +import tables + +def afunction(x): + return x + 1 + +xmin = -10 +xmax = 10 +xscl = 2 + +tables.function(xmin, xmax, xscl, afunction, headerrow=True) +``` + +![](../images/function%20to%20table.png) + +#### Output multiple functions as table + +```py +import tables + +def function1(x): + return 2 * x + +def function2(x): + return x ** 2 + +xmin = -10 +xmax = 10 +xscl = 2 + +# Function parameter and return value can be any data type, as long as they are the same +functions = [function1, function2] + +tables.functions(xmin, xmax, xscl, functions, headerrow=True) +``` + +![](../images/multiple%20functions%20to%20table.png) + +### Options + +#### Header row + +Option: `headerrow`\ +Default value: `False` + +Header rows are bolded, centered and have a border. + +#### Header column + +Option: `headercolumn`\ +Default value: `False` + +Header columns are bolded, centered and have a border. + +#### Table border + +Option: `tableborder`\ +Default value: `False` + +#### Cell border + +Option: `cellborder`\ +Default value: `False` + +#### Cell padding + +Option: `padding`\ +Default value: `1` + +#### Alignment + +Option: `alignment`\ +Values: + +* `False` (left, default) +* `True` (right) + +#### Title + +Option: `title`\ +Default value: `None` + +The title is word wrapped based on the current width of the terminal. Handles newlines, tabs and [Unicode characters](https://en.wikipedia.org/wiki/List_of_Unicode_characters). + +#### Border style + +Option: `style`\ +Values: + +0. ASCII + + ![](../images/ASCII%20table.png) +1. Basic + + ![](../images/basic%20table.png) +2. Light (default) + + ![](../images/light%20table.png) +3. Heavy + + ![](../images/heavy%20table.png) +4. Double + + ![](../images/double%20table.png) +5. Light Dashed + + ![](../images/light%20dashed%20table.png) +6. Heavy Dashed + + ![](../images/heavy%20dashed%20table.png) + +## Graphs/Plots + +### Usage + +Requires Python 3.5 or greater and the [wcwidth library](https://pypi.org/project/wcwidth/), which users can install with: `pip3 install wcwidth`. + +Complete versions of all of the examples below and more can be found in the [test.py](test.py) file. + +Run with: `python3 test.py`. + +If `height` is `0`, it will be set to the current height of the terminal (number of rows times four). If `width` is `0`, it will be set to the current width of the terminal (number of columns times two). + +#### Output array as plot + +```py +import graphs + +height = 160 +width = 160 + +xmin = -20 +xmax = 20 +ymin = -20 +ymax = 20 + +# Set array + +graphs.array(height, width, xmin, xmax, ymin, ymax, array) +``` + +If `xmin` and `xmax` are both `0`, they will be set to the respective minimum and maximum values of x in the array. If `ymin` and `ymax` are both `0`, they will be set to the respective minimum and maximum values of y in the array. + +![](../images/array%20to%20plot.png) + +#### Output single function as graph + +```py +import graphs + +def afunction(x): + return x + 1 + +height = 160 +width = 160 + +xmin = -20 +xmax = 20 +ymin = -20 +ymax = 20 + +graphs.function(height, width, xmin, xmax, ymin, ymax, afunction) +``` + +![](../images/function%20to%20graph.png) + +#### Output multiple functions as graph + +```py +import graphs + +def function1(x): + return 2 * x + +def function2(x): + return x ** 2 + +height = 160 +width = 160 + +xmin = -20 +xmax = 20 +ymin = -20 +ymax = 20 + +# Function parameter and return value can be any data type, as long as they are the same +functions = [function1, function2] + +graphs.functions(height, width, xmin, xmax, ymin, ymax, functions) +``` + +![](../images/multiple%20functions%20to%20graph.png) + +### Options + +#### Border/Axis + +Option: `border`\ +Default value: `False` + +#### Axis labels + +Option: `axislabel`\ +Default value: `False` + +Requires `border` to be `False`. + +#### Axis units labels + +Option: `axisunitslabel`\ +Default value: `False` + +Requires `border` and `axislabel` to be `False`. + +#### Title + +Option: `title`\ +Default value: `None` + +The title is word wrapped based on the current width of the terminal. Handles newlines, tabs and [Unicode characters](https://en.wikipedia.org/wiki/List_of_Unicode_characters). + +#### Axis/Border style + +Option: `style`\ +Values: + +0. ASCII + + ![](../images/ASCII%20graph.png) +1. Basic + + ![](../images/basic%20graph.png) +2. Light (default) + + ![](../images/light%20graph.png) +3. Heavy + + ![](../images/heavy%20graph.png) +4. Double + + ![](../images/double%20graph.png) +5. Light Dashed + + ![](../images/light%20dashed%20graph.png) +6. Heavy Dashed + + ![](../images/heavy%20dashed%20graph.png) + +#### Graph/Plot Color + +Option: `color`\ +Values: + +0. System default +1. Black +2. Red (default) +3. Green +4. Yellow +5. Blue +6. Cyan +7. Light gray +8. Dark gray +9. Light red +10. Light green +11. Light yellow +12. Light blue +13. Light cyan +14. White + +See [here](https://misc.flogisoft.com/bash/tip_colors_and_formatting#foreground_text) for examples of the colors. + +Only used for plots and when graphing a single function. + +When graphing multiple functions, colors `2` - `14` are used inorder. Color `0` is used where the functions cross. + +##### Plot + +![](../images/plot%20colors.png) + +##### Graph + +![](../images/graph%20colors.png) diff --git a/python/graphs.py b/python/graphs.py new file mode 100644 index 0000000..b7effef --- /dev/null +++ b/python/graphs.py @@ -0,0 +1,458 @@ +#!/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) diff --git a/python/tables.py b/python/tables.py new file mode 100644 index 0000000..c2aee30 --- /dev/null +++ b/python/tables.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Teal Dulcet, CS546 + +from __future__ import division, print_function, unicode_literals +import sys +import shutil +import re +import textwrap +from wcwidth import wcswidth +from typing import List, Optional, Any, Sequence, Callable +import locale + +locale.setlocale(locale.LC_ALL, '') + +styles = [ + ["-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"], # ASCII + ["—", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"], # Basic + ["─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light + ["━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"], # Heavy + ["═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"], # Double + ["╌", "╎", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light Dashed + ["╍", "╏", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"] # Heavy Dashed +] +# [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]] #No border + +ansi = re.compile(r'\x1B\[(?:[0-9]+(?:;[0-9]+)*)?m') + + +def strcol(str: str) -> int: + """Returns the number of columns that the given string would take up if printed.""" + str = ansi.sub('', str) + 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 table(array: List[List[str]], headerrow: bool=False, headercolumn: bool=False, tableborder: bool=True, cellborder: bool=False, padding: int=1, alignment: bool=False, title: Optional[str]=None, style: int=2) -> int: + """Output char array as table""" + if not array: + return 1 + + if not (0 >= style > len(styles)): + return 1 + + rows = len(array) + columns = len(array[0]) + + columnwidth = [max(strcol(i) for i in j) for j in zip(*array)] + + width = sum(columnwidth) + + w = shutil.get_terminal_size() + + if tableborder or cellborder or headerrow or headercolumn: + width += (((2 * padding) + 1) * columns) + (1 if tableborder else -1) + else: + width += (2 * padding) * columns + + if width > w.columns: + print("The width of the table ({0}) is greater then the width of the terminal ({1}).".format( + width, w.columns), file=sys.stderr) + return 1 + + if title: + print(textwrap.fill(title, width=w.columns)) + + strm = "" + + if tableborder: + strm += styles[style][2] + + for j in range(columns): + strm += styles[style][0] * ((2 * padding) + columnwidth[j]) + + if j == (columns - 1): + strm += styles[style][4] + "\n" + elif cellborder or headerrow or (j == 0 and headercolumn): + strm += styles[style][3] + else: + strm += styles[style][0] + + for i in range(rows): + for j in range(columns): + if (j == 0 and tableborder) or (j > 0 and cellborder) or (i == 0 and j > 0 and headerrow) or (j == 1 and headercolumn): + strm += styles[style][1] + elif tableborder or (i > 0 and j > 0 and headerrow) or (j > 1 and headercolumn): + strm += " " + + awidth = columnwidth[j] - (strcol(array[i][j]) - len(array[i][j])) + + if (i == 0 and headerrow) or (j == 0 and headercolumn): + strm += " " * padding + + strm += "\033[1m" + array[i][j].center(awidth) + "\033[22m" + + strm += " " * padding + else: + strm += " " * padding + + if alignment: + strm += array[i][j].rjust(awidth) + else: + strm += array[i][j].ljust(awidth) + + strm += " " * padding + + if tableborder: + strm += styles[style][1] + + strm += "\n" + + if tableborder: + if i == (rows - 1): + strm += styles[style][8] + elif cellborder or (i == 0 and headerrow) or headercolumn: + strm += styles[style][5] + + if (i == (rows - 1) and tableborder) or (i < (rows - 1) and cellborder) or (i == 0 and headerrow) or (i < (rows - 1) and headercolumn): + for j in range(columns): + if (i == (rows - 1) and tableborder) or (i < (rows - 1) and cellborder) or (i == 0 and headerrow) or (i < (rows - 1) and j == 0 and headercolumn): + strm += styles[style][0] * ((2 * padding) + columnwidth[j]) + elif i < (rows - 1) and headercolumn: + strm += " " * ((2 * padding) + columnwidth[j]) + + if j == (columns - 1): + if tableborder: + if i == (rows - 1): + strm += styles[style][10] + elif cellborder or (i == 0 and headerrow): + strm += styles[style][7] + elif headercolumn: + strm += styles[style][1] + + strm += "\n" + else: + if i == (rows - 1) and tableborder: + if cellborder or (j == 0 and headercolumn): + strm += styles[style][9] + else: + strm += styles[style][0] + elif (i < (rows - 1) and cellborder) or ((i == 0 and headerrow) and (j == 0 and headercolumn)): + strm += styles[style][6] + elif i == 0 and headerrow: + strm += styles[style][9] + elif i < (rows - 1) and headercolumn: + if j == 0: + strm += styles[style][7] + else: + strm += " " + + print(strm) + + return 0 + + +def array(aarray: Sequence[Sequence[Any]], aheaderrow: Optional[Sequence[Any]], aheadercolumn: Optional[Sequence[Any]], headerrow: bool=False, headercolumn: bool=False, tableborder: bool=True, cellborder: bool=False, padding: int=1, alignment: bool=False, title: Optional[str]=None, style: int=2) -> int: + """Convert array to char array and output as table""" + if not aarray: + return 1 + + rows = len(aarray) + columns = len(aarray[0]) + + if not all(len(x) == columns for x in aarray): + print("Error: The rows of the array must have the same number of columns.", file=sys.stderr) + return 1 + + if aheaderrow: + rows += 1 + + if aheadercolumn: + columns += 1 + + if aheaderrow and len(aheaderrow) != columns: + print("Error: The header row must have the same number of columns as the array.", file=sys.stderr) + return 1 + + if aheadercolumn and len(aheadercolumn) != (rows - 1 if aheaderrow else rows): + print("Error: The header column must have the same number of rows as the array.", file=sys.stderr) + return 1 + + aaarray = [["" for j in range(columns)] for i in range(rows)] + + i = 0 + + if aheaderrow: + for j in range(columns): + aaarray[i][j] = aheaderrow[j] + + i += 1 + + j = 0 + + ii = 0 + for i in range(i, rows): + if aheadercolumn: + aii = i + + if aheaderrow: + aii -= 1 + + aaarray[i][j] = aheadercolumn[aii] + + j += 1 + + jj = 0 + for j in range(j, columns): + aaarray[i][j] = str(aarray[ii][jj]) + + jj += 1 + + j = 0 + ii += 1 + + return table(aaarray, headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) + + +def functions(xmin: float, xmax: float, xscl: float, afunctions: Sequence[Callable[[float], float]], headerrow: bool=False, headercolumn: bool=False, tableborder: bool=True, cellborder: bool=False, padding: int=1, alignment: bool=False, title: Optional[str]=None, style: int=2) -> int: + """Convert one or more functions to array and output as table""" + if len(afunctions) == 0: + return 1 + + if xmin >= xmax: + print("xmin must be less than xmax.", file=sys.stderr) + return 1 + + if xscl <= 0: + print("xscl must be greater than zero.", file=sys.stderr) + return 1 + + rows = int(((xmax - xmin) * xscl)) + 1 + columns = len(afunctions) + 1 + + aaheaderrow = ["x", "y"] + length = len(aaheaderrow) + + aheaderrow = [""] * columns + + for j in range(columns): + if j < (length - 1) or len(afunctions) == 1: + aheaderrow[j] = aaheaderrow[j] + else: + aheaderrow[j] = aaheaderrow[length - 1] + str(j - length + 2) + + aarray = [[0 for j in range(columns)] for i in range(rows)] + + for i in range(rows): + aarray[i][0] = (i / xscl) + xmin + + for j, function in enumerate(afunctions): + aarray[i][j + 1] = function(aarray[i][0]) + + return array(aarray, aheaderrow, None, headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) + + +def function(xmin: float, xmax: float, xscl: float, afunction: Callable[[float], float], headerrow: bool=False, headercolumn: bool=False, tableborder: bool=True, cellborder: bool=False, padding: int=1, alignment: bool=False, title: Optional[str]=None, style: int=2) -> int: + """Convert single function to array and output as table""" + return functions(xmin, xmax, xscl, [afunction], headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) diff --git a/python/test.py b/python/test.py new file mode 100644 index 0000000..9df8439 --- /dev/null +++ b/python/test.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Teal Dulcet, CS546 + +from __future__ import division, print_function, unicode_literals +import random +import sys +import math +import graphs +import tables + + +def afunction(x): return x + 1 +def function1(x): return 2 * x +def function2(x): return x ** 2 + + +rows = 5 +columns = 5 + +xmin = -10 +xmax = 10 +xscl = 2 + +print("\nOutput array as table\n") +array = [[random.randint(0, sys.maxsize) + for j in range(columns)] for i in range(rows)] +for k in range(len(tables.styles)): + tables.array(array, None, None, style=k) + +array = [[random.random() for j in range(columns)] for i in range(rows)] +for k in range(len(tables.styles)): + tables.array(array, None, None, style=k) + +print("\nOutput char array as table\n") +array = [["Header row/column 1", "Header row 2", "Header row 3", "Header row 4", "Header row 5"], + ["Header column 2", "Data 1", "Data 2", "Data 3", "Data 4"], + ["Header column 3", "Data 5", "Data 6", "Data 7", "Data 8"], + ["Header column 4", "Data 9", "Data 10", "Data 11", "Data 12"], + ["Header column 5", "Data 13", "Data 14", "Data 15", "Data 16"]] +for k in range(len(tables.styles)): + tables.array(array, None, None, headerrow=True, headercolumn=True, style=k) + +print("\nOutput array as table with separate header row and column\n") +array = [["Data {0:n}".format(i + j) for j in range(4)] + for i in range(1, 4 * 4 + 1, 4)] +headerrow = ["Header row/column 1", "Header row 2", + "Header row 3", "Header row 4", "Header row 5"] +headercolumn = ["Header column 2", "Header column 3", + "Header column 4", "Header column 5"] + +for k in range(len(tables.styles)): + tables.array(array, headerrow, headercolumn, headerrow=True, headercolumn=True, cellborder=True, style=k) + tables.array(array, headerrow, headercolumn, headerrow=True, headercolumn=True, style=k) + tables.array(array, headerrow[:-1], None, headerrow=True, style=k) + tables.array(array, None, [headerrow[0]] + headercolumn[:-1], headercolumn=True, style=k) + tables.array(array, None, None, cellborder=True, style=k) + tables.array(array, None, None, tableborder=False, style=k) + tables.array(array, headerrow, headercolumn, tableborder=False, headerrow=True, headercolumn=True, style=k) + tables.array(array, headerrow[:-1], None, tableborder=False, headerrow=True, style=k) + tables.array(array, None, [headerrow[0]] + headercolumn[:-1], tableborder=False, headercolumn=True, style=k) + tables.array(array, None, None, tableborder=False, cellborder=True, style=k) + +array = [[bool(random.getrandbits(1)) for j in range(columns)] + for i in range(rows)] +for k in range(len(tables.styles)): + tables.array(array, None, None, style=k) + +print("\nOutput sorted array as table\n") +array = ([random.randint(0, sys.maxsize) + for j in range(columns)] for i in range(rows)) +sortdimension = 0 +array = sorted(array, key=lambda x: x[sortdimension]) +for k in range(len(tables.styles)): + tables.array(array, None, None, style=k) + +print("\nOutput single function as table\n") +for k in range(len(tables.styles)): + tables.function(xmin, xmax, xscl, afunction, headerrow=True, style=k) + +print("\nOutput multiple functions as table\n") +for k in range(len(tables.styles)): + tables.functions(xmin, xmax, xscl, [ + function1, function2], headerrow=True, style=k) + +height = 160 +width = 160 + +xmin = -20 +xmax = 20 +ymin = -20 +ymax = 20 + +print("\nOutput array as plot\n") +array = [[i + j for j in range(2)] for i in range(10)] +for k in range(len(graphs.styles)): + graphs.array(height, width, xmin, xmax, ymin, ymax, array, style=k) + +print("\nOutput single function as graph\n") +for k in range(len(graphs.styles)): + graphs.function(height, width, xmin, xmax, ymin, ymax, afunction, style=k) + +print("\nOutput multiple functions as graph\n") +for k in range(len(graphs.styles)): + graphs.functions(height, width, xmin, xmax, ymin, + ymax, [function1, function2], style=k) + +for k in range(len(graphs.styles)): + graphs.functions(height, width, -(2 * math.pi), 2 * + math.pi, -4, 4, [math.sin, math.cos, math.tan], style=k) diff --git a/tables.cpp b/tables.cpp index 960f1b6..3594218 100644 --- a/tables.cpp +++ b/tables.cpp @@ -136,27 +136,13 @@ int main() // Output char array as table cout << "\nOutput char array as table\n\n"; { - const char *const aarray[rows][columns] = { + const char *const array[rows][columns] = { {"Header row/column 1", "Header row 2", "Header row 3", "Header row 4", "Header row 5"}, {"Header column 2", "Data 1", "Data 2", "Data 3", "Data 4"}, {"Header column 3", "Data 5", "Data 6", "Data 7", "Data 8"}, {"Header column 4", "Data 9", "Data 10", "Data 11", "Data 12"}, {"Header column 5", "Data 13", "Data 14", "Data 15", "Data 16"}}; - char ***array; - array = new char **[rows]; - for (unsigned int i = 0; i < rows; ++i) - array[i] = new char *[columns]; - - for (unsigned int j = 0; j < columns; ++j) - { - for (unsigned int i = 0; i < rows; ++i) - { - array[i][j] = new char[strlen(aarray[i][j]) + 1]; - strcpy(array[i][j], aarray[i][j]); - } - } - tableoptions aoptions; aoptions.headerrow = true; aoptions.headercolumn = true; @@ -167,18 +153,6 @@ int main() table(rows, columns, array, NULL, NULL, aoptions); } - - if (array != NULL) - { - for (unsigned int i = 0; i < rows; ++i) - { - for (unsigned int j = 0; j < columns; ++j) - delete[] array[i][j]; - - delete[] array[i]; - } - delete[] array; - } } // Output array as table with separate header row and column cout << "\nOutput array as table with separate header row and column\n\n"; @@ -186,7 +160,7 @@ int main() const size_t rows = 4; const size_t columns = 4; - const char *const aarray[rows][columns] = { + const char *const array[rows][columns] = { {"Data 1", "Data 2", "Data 3", "Data 4"}, {"Data 5", "Data 6", "Data 7", "Data 8"}, {"Data 9", "Data 10", "Data 11", "Data 12"}, @@ -195,20 +169,6 @@ int main() const char *const headerrow[] = {"Header row/column 1", "Header row 2", "Header row 3", "Header row 4", "Header row 5"}; const char *const headercolumn[] = {"Header column 2", "Header column 3", "Header column 4", "Header column 5"}; - char ***array; - array = new char **[rows]; - for (unsigned int i = 0; i < rows; ++i) - array[i] = new char *[columns]; - - for (unsigned int j = 0; j < columns; ++j) - { - for (unsigned int i = 0; i < rows; ++i) - { - array[i][j] = new char[strlen(aarray[i][j]) + 1]; - strcpy(array[i][j], aarray[i][j]); - } - } - tableoptions aoptions; aoptions.headerrow = true; aoptions.headercolumn = true; @@ -219,18 +179,6 @@ int main() table(rows, columns, array, headerrow, headercolumn, aoptions); } - - if (array != NULL) - { - for (unsigned int i = 0; i < rows; ++i) - { - for (unsigned int j = 0; j < columns; ++j) - delete[] array[i][j]; - - delete[] array[i]; - } - delete[] array; - } } { bool **array; diff --git a/tables.hpp b/tables.hpp index 4a363ec..7333739 100644 --- a/tables.hpp +++ b/tables.hpp @@ -330,11 +330,8 @@ int table(const size_t rows, const size_t columns, char ***array, const tableopt // Convert array to char array and output as table template -int table(size_t rows, size_t columns, T **array, const char *const headerrow[], const char *const headercolumn[], const tableoptions &aoptions) +int table(size_t rows, size_t columns, const T &array, const char *const headerrow[], const char *const headercolumn[], const tableoptions &aoptions) { - if (array == NULL) - return 1; - unsigned int i = 0; unsigned int j = 0;