diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..99bf0a8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: cpp +compiler: + - clang + - gcc +matrix: + include: + - os: linux + dist: trusty + - os: linux + dist: xenial +install: + - sudo apt-get -yqq update + - sudo apt-get -yqq install cppcheck +script: + - g++ -Wall -g -fsanitize=address tables.cpp -o gcc_tables + - ./gcc_tables + - g++ -Wall -g -fsanitize=address graphs.cpp -o gcc_graphs + - ./gcc_graphs + - clang++ -Wall -g -fsanitize=address tables.cpp -o clang_tables + - ./clang_tables + - clang++ -Wall -g -fsanitize=address graphs.cpp -o clang_graphs + - ./clang_graphs + - cppcheck --error-exitcode=1 . diff --git a/README.md b/README.md index bdd555f..847d943 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,554 @@ -# Tables-and-Graphs +[![Build Status](https://travis-ci.org/tdulcet/Tables-and-Graphs.svg?branch=master)](https://travis-ci.org/tdulcet/Tables-and-Graphs) + +# Tables and Graphs + C++ Console Table and Graph/Plot Libraries + +Copyright © 2018 Teal Dulcet + +These header only libraries use [box-drawing](https://en.wikipedia.org/wiki/Box-drawing_character#Unicode), [Braille](https://en.wikipedia.org/wiki/Braille_Patterns), [fraction](https://en.wikipedia.org/wiki/Number_Forms) and other Unicode characters and [terminal colors and formatting](https://misc.flogisoft.com/bash/tip_colors_and_formatting) to output tables and graphs/plots to the console. + +Please visit [tealdulcet.com](https://www.tealdulcet.com/) to support these libraries and my other software development. + +## Tables + +### Usage + +Complete versions of all of the examples below and more can be found in the [tables.cpp](tables.cpp) file. + +Compile with: `g++ -Wall -g -O3 tables.cpp -o tables`. + +Run with: `./tables`. + +#### Output char array as table + +```cpp +#include "tables.hpp" + +using namespace std; + +int main() +{ + size_t rows = 5; + size_t columns = 5; + + char ***array; + + // Allocate and set array + + tableoptions aoptions; + aoptions.headerrow = true; + aoptions.headercolumn = true; + + table(rows, columns, array, NULL, NULL, aoptions); + + // Deallocate array + + return 0; +} +``` + +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 + +```cpp +#include "tables.hpp" + +using namespace std; + +int main() +{ + size_t rows = 4; + size_t columns = 4; + + const char* headerrow[] = {"Header row/column 1", "Header row 2", "Header row 3", "Header row 4", "Header row 5"}; + const char* headercolumn[] = {"Header column 2", "Header column 3", "Header column 4", "Header column 5"}; + + char ***array; + + // Allocate and set array + + tableoptions aoptions; + aoptions.headerrow = true; + aoptions.headercolumn = true; + + table(rows, columns, array, headerrow, headercolumn, aoptions); + + // Deallocate array + + return 0; +} +``` + +Output same as example above. + +#### Output array as table + +```cpp +#include "tables.hpp" + +using namespace std; + +int main() +{ + size_t rows = 5; + size_t columns = 5; + + double **array; // array can be any data type + + // Allocate and set array + + table(rows, columns, array, NULL, NULL, tabledefaultoptions); + + // Deallocate array + + return 0; +} +``` + +![](images/array%20to%20table.png) + +#### Output sorted array as table + +```cpp +#include +#include "tables.hpp" + +using namespace std; + +int dimensions; // Number of columns +int sortdimension; // Column to sort by + +template +bool compare(const T *a, const T *b) +{ + if (a[sortdimension] == b[sortdimension]) + for (int i = 0; i < dimensions; ++i) + if (sortdimension != i and a[i] != b[i]) + return a[i] < b[i]; + + return a[sortdimension] < b[sortdimension]; +} + +int main() +{ + size_t rows = 5; + size_t columns = 5; + + int **array; // array can be any data type + + // Allocate and set array + + dimensions = columns; + sortdimension = 0; + + sort(array, array + rows, compare); + + table(rows, columns, array, NULL, NULL, tabledefaultoptions); + + // Deallocate array + + return 0; +} +``` + +![](images/sorted%20array%20to%20table.png) + +#### Output single function as table + +```cpp +#include "tables.hpp" + +using namespace std; + +double afunction(double x) +{ + return x + 1; +} + +int main() +{ + double xmin = -10; + double xmax = 10; + double xscl = 2; + + tableoptions aoptions; + aoptions.headerrow = true; + + table(xmin, xmax, xscl, afunction, aoptions); + + return 0; +} +``` + +![](images/function%20to%20table.png) + +#### Output multiple functions as table + +```cpp +#include +#include "tables.hpp" + +using namespace std; + +double function1(double x) +{ + return 2 * x; +} + +double function2(double x) +{ + return pow(x, 2); +} + +int main() +{ + double xmin = -10; + double xmax = 10; + double xscl = 2; + + size_t numfunctions = 2; + + // Function parameter and return value can be any data type, as long as they are the same + double (*functions[])(double) = {function1, function2}; + + tableoptions aoptions; + aoptions.headerrow = true; + + table(xmin, xmax, xscl, numfunctions, functions, aoptions); + + return 0; +} +``` + +![](images/multiple%20functions%20to%20table.png) + +### Options + +#### Header row + +Option: `headerrow` + +Default value: `false` + +Header rows are bolded and centered. + +#### Header column + +Option: `headercolumn` + +Default value: `false` + +Header columns are bolded and centered. + +#### Table border + +Option: `tableborder` + +Default value: `true` + +#### Cell border + +Option: `cellborder` + +Default value: `false` + +Requires `tableborder` to be `true`. + +#### Cell padding + +Option: `padding` + +Default value: `1` + +#### Alignment + +Option: `alignment` + +Values: + +* `left` (default) +* `right` + +#### bool to alpha + +Option: `boolalpha` + +Default value: `false` + +#### Title + +Option: `title` + +Default value: `NULL` + +The title is word wraped based on the current width of the terminal, using [this](https://gist.github.com/tdulcet/819821ca69501822ad3f84a060c640a0) solution. 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) + +### Other C++ Console Tables Libraries + +* [C++ Text Table](https://github.com/haarcuba/cpp-text-table) (must specify every cell individually in there data structure, limited options, no Unicode support, no header row or column support) +* [Cpp Console Table](https://github.com/Oradle/CppConsoleTable) (must specify every cell individually in there data structure, no Unicode support, no header row or column support) +* [ConsoleTable](https://github.com/766F6964/ConsoleTable) (requires C++11, must specify entire row at once in there data structure, no header column support) + +## Graphs/Plots + +### Usage + +Complete versions of all of the examples below and more can be found in the [graphs.cpp](graphs.cpp) file. + +Compile with: `g++ -Wall -g -O3 graphs.cpp -o graphs`. + +Run with: `./graphs`. + +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 + +```cpp +#include "graphs.hpp" + +using namespace std; + +int main() +{ + size_t height = 160; + size_t width = 160; + + long double xmin = -20; + long double xmax = 20; + long double ymin = -20; + long double ymax = 20; + + size_t rows = 10; + + double **array; // array can be any data type, but must have exactly two columns + + // Allocate and set array + + graph(height, width, xmin, xmax, ymin, ymax, rows, array, graphdefaultoptions); + + // Deallocate array + + return 0; +} +``` + +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 + +```cpp +#include "graphs.hpp" + +using namespace std; + +double afunction(double x) +{ + return x + 1; +} + +int main() +{ + size_t height = 160; + size_t width = 160; + + long double xmin = -20; + long double xmax = 20; + long double ymin = -20; + long double ymax = 20; + + graph(height, width, xmin, xmax, ymin, ymax, afunction, graphdefaultoptions); + + return 0; +} +``` + +![](images/function%20to%20graph.png) + +#### Output multiple functions as graph + +```cpp +#include "graphs.hpp" + +using namespace std; + +double function1(double x) +{ + return 2 * x; +} + +double function2(double x) +{ + return pow(x, 2); +} + +int main() +{ + size_t height = 160; + size_t width = 160; + + long double xmin = -20; + long double xmax = 20; + long double ymin = -20; + long double ymax = 20; + + size_t numfunctions = 2; + + // Function parameter and return value can be any data type, as long as they are the same + double (*functions[])(double) = {function1, function2}; + + graph(height, width, xmin, xmax, ymin, ymax, numfunctions, functions, graphdefaultoptions); + + return 0; +} +``` + +![](images/multiple%20functions%20to%20graph.png) + +### Options + +#### Border/Axis + +Option: `border` + +Default value: `true` + +#### Axis labels + +Option: `axislabel` + +Default value: `true` + +Requires `border` to be `true`. + +#### Axis units labels + +Option: `axisunitslabel` + +Default value: `true` + +Requires `border` and `axislabel` to be `true`. + +#### Title + +Option: `title` + +Default value: `NULL` + +The title is word wraped based on the current width of the terminal, using [this](https://gist.github.com/tdulcet/819821ca69501822ad3f84a060c640a0) solution. 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) + +### Other C++ Console Graphs/Plots Libraries + +* [C++ terminal plotting library](https://github.com/fbbdev/plot) (requires C++14, based on [UnicodePlots.jl](https://github.com/Evizero/UnicodePlots.jl), no documentation and very difficult to use, although supports animations) + +## Contributing + +Pull requests welcome! Ideas for contributions: + +* Add more options + * Add an option to print a border around graphs/plots +* Add more examples +* Improve the performance +* Handle newlines, tabs and formatted text in the tables +* Handle formatted text in the table and graph/plot titles +* Support more graph/plot colors + * Support combining colors when functions cross +* Support plotting multiple arrays of different sizes +* Port to other languages (C, Java, Rust, etc.) diff --git a/graphs.cpp b/graphs.cpp new file mode 100644 index 0000000..cd733e6 --- /dev/null +++ b/graphs.cpp @@ -0,0 +1,141 @@ +// Teal Dulcet, CS546 + +// Compile: g++ -Wall -g -O3 graphs.cpp -o graphs + +// Run: ./graphs + +#include +#include "graphs.hpp" + +using namespace std; + +long double afunction(long double x) +{ + return x + 1; +} + +long double function1(long double x) +{ + return 2 * x; +} + +long double function2(long double x) +{ + return pow(x, 2); +} + +long double function3(long double x) +{ + return sin(x); +} + +long double function4(long double x) +{ + return cos(x); +} + +long double function5(long double x) +{ + return tan(x); +} + +int main() +{ + const size_t height = 160; + const size_t width = 160; + + const long double xmin = -20; + const long double xmax = 20; + const long double ymin = -20; + const long double ymax = 20; + + const size_t rows = 10; + const size_t columns = 2; + + // Output array as plot + cout << "\nOutput array as plot\n\n"; + { + long double **array; + array = new long double *[rows]; + for (unsigned int i = 0; i < rows; ++i) + array[i] = new long double[columns]; + + for (unsigned int i = 0; i < rows; ++i) + for (unsigned int j = 0; j < columns; ++j) + array[i][j] = i + j; //rand(); + + graphoptions aoptions; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + graph(height, width, xmin, xmax, ymin, ymax, rows, array, aoptions); + } + + if (array != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + delete[] array[i]; + + delete[] array; + } + } + // Output single function as graph + cout << "\nOutput single function as graph\n\n"; + { + graphoptions aoptions; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + graph(height, width, xmin, xmax, ymin, ymax, afunction, aoptions); + } + } + // Output multiple functions as graph + cout << "\nOutput multiple functions as graph\n\n"; + { + long double (*functions[])(long double) = {function1, function2}; + + graphoptions aoptions; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + graph(height, width, xmin, xmax, ymin, ymax, 2, functions, aoptions); + } + } + { + const long double xmin = -(2 * M_PI); + const long double xmax = 2 * M_PI; + const long double ymin = -4; + const long double ymax = 4; + + long double (*functions[])(long double) = {function3, function4, function5}; + + graphoptions aoptions; + aoptions.axisunitslabel = false; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + graph(height, width, xmin, xmax, ymin, ymax, 3, functions, aoptions); + } + + /*aoptions.style = 2; + + for (unsigned int k = 10; k < 300; ++k) + { + cout << "\e[1;1H" << "\e[2J"; + + graph(k, k, xmin, xmax, ymin, ymax, 3, functions, aoptions); + + usleep(200000); + }*/ + } + + return 0; +} diff --git a/graphs.hpp b/graphs.hpp new file mode 100644 index 0000000..c35dbef --- /dev/null +++ b/graphs.hpp @@ -0,0 +1,725 @@ +// Teal Dulcet, CS546 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +const char *const styles[][11] = { + {"-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, //ASCII + {"—", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, //Basic + {"─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, //Light + {"━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"}, //Heavy + {"═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"}, //Double + {"╌", "╎", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, //Light Dashed + {"╍", "╏", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"} //Heavy Dashed +}; +// {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}};//No border + +const char *const colors[] = {"\e[39m", "\e[30m", "\e[31m", "\e[32m", "\e[33m", "\e[34m", "\e[35m", "\e[36m", "\e[37m", "\e[90m", "\e[91m", "\e[92m", "\e[93m", "\e[94m", "\e[95m", "\e[96m", "\e[97m"}; + +const char *const dots[] = {"⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏", "⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟", "⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯", "⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿", "⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏", "⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟", "⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯", "⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿", "⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏", "⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟", "⢠", "⢡", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯", "⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿", "⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏", "⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟", "⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿"}; + +const int values[][4] = {{0x1, 0x2, 0x4, 0x40}, {0x8, 0x10, 0x20, 0x80}}; + +const char *const fractions[] = {"¼", "½", "¾", "⅐", "⅑", "⅒", "⅓", "⅔", "⅕", "⅖", "⅗", "⅘", "⅙", "⅚", "⅛", "⅜", "⅝", "⅞"}; + +const long double fractionvalues[] = {1.0L / 4.0L, 1.0L / 2.0L, 3.0L / 4.0L, 1.0L / 7.0L, 1.0L / 9.0L, 1.0L / 10.0L, 1.0L / 3.0L, 2.0L / 3.0L, 1.0L / 5.0L, 2.0L / 5.0L, 3.0L / 5.0L, 4.0L / 5.0L, 1.0L / 6.0L, 5.0L / 6.0L, 1.0L / 8.0L, 3.0L / 8.0L, 5.0L / 8.0L, 7.0L / 8.0L}; + +struct graphoptions +{ + bool border; + bool axislabel; + bool axisunitslabel; + char *title; + unsigned int style; + unsigned int color; + graphoptions(void); + ~graphoptions(void); +}; + +graphoptions::graphoptions(void) +{ + border = true; + axislabel = true; + axisunitslabel = true; + style = 2; + color = 2; + title = NULL; +} + +graphoptions::~graphoptions(void) +{ +} + +const graphoptions graphdefaultoptions; + +// Number of columns needed to represent the string +// Adapted from: https://stackoverflow.com/a/31124065 +int strcol(const char *const str) +{ + size_t length = strlen(str); + for (size_t i = 0; i < length; ++i) + if (iscntrl(str[i])) + { + cerr << "\nError! Control character in string.\n"; + cout << "Control character: " << (int)str[i] << "\n"; + } + + length = mbstowcs(NULL, str, 0); + if (length == (size_t)-1) + { + cerr << "\nError! mbstowcs failed. Invalid multibyte character.\n"; + exit(1); + } + ++length; + + wchar_t *wcstring = new wchar_t[length]; + + if (mbstowcs(wcstring, str, length) == (size_t)-1) + { + if (wcstring != NULL) + delete[] wcstring; + + cerr << "\nError! mbstowcs failed. Invalid multibyte character.\n"; + exit(1); + } + + int width = wcswidth(wcstring, length); + if (width == -1) + { + cerr << "\nError! wcswidth failed. Nonprintable wide character.\n"; + exit(1); + } + + if (wcstring != NULL) + delete[] wcstring; + + return width; +} + +// Word wrap +// Source: https://gist.github.com/tdulcet/819821ca69501822ad3f84a060c640a0 +// Adapted from: https://stackoverflow.com/a/42016346 and https://stackoverflow.com/a/13094734 +string wrap(const char *const str, const size_t line_length) +{ + char words[strlen(str) + 1]; + strcpy(words, str); + string wrapped; + + size_t index = 0; + size_t linelen = 0; + while (words[index] != '\0') + { + if (words[index] == '\n') + { + linelen = 0; + } + else if (isspace(words[index])) + { + size_t tempindex = index + 1; + size_t templinelen = linelen; + while (!isspace(words[tempindex]) and words[tempindex] != '\0') + { + ++templinelen; + + ++tempindex; + } + + char temp[templinelen + 1]; + strncpy(temp, words + (index - linelen), templinelen); + temp[templinelen] = '\0'; + + size_t width = strcol(temp); + + if (width >= line_length) + { + words[index] = '\n'; + linelen = 0; + } + } + + if (words[index] == '\t') + linelen += 8 - (linelen % 8); + else if (words[index] != '\n') + ++linelen; + + ++index; + } + wrapped = words; + return wrapped; +} + +// Convert fractions and constants to Unicode characters +size_t outputlabel(const long double label, ostringstream &strm) +{ + bool output = false; + + long double intpart = 0; + long double fractionpart = abs(modf(label, &intpart)); + + for (unsigned int i = 0; i < (sizeof fractions / sizeof fractions[0]) and !output; ++i) + { + if (abs(fractionpart - fractionvalues[i]) < DBL_EPSILON) + { + if (intpart != 0) + strm << intpart; + + strm << fractions[i]; + + output = true; + } + } + + if (!output and fmod(label, M_PI) == 0) + { + const char symbol[] = "π"; + + intpart = label / M_PI; + + if (intpart == -1) + strm << "-"; + else if (intpart != 1) + strm << intpart; + + strm << symbol; + + output = true; + } + else if (!output and fmod(label, M_E) == 0) + { + const char symbol[] = "e"; + + intpart = label / M_E; + + if (intpart == -1) + strm << "-"; + else if (intpart != 1) + strm << intpart; + + strm << symbol; + + output = true; + } + + if (!output) + strm << label; + + size_t length = strcol(strm.str().c_str()); + + return length; +} + +// Output graph +int graph(const size_t height, const size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, unsigned short **array, const graphoptions &aoptions) +{ + if (array == NULL) + return 1; + + const bool border = aoptions.border; + const bool axislabel = aoptions.axislabel; + const bool axisunitslabel = aoptions.axisunitslabel; + const char *const title = aoptions.title; + const unsigned int style = aoptions.style; + + if (style >= (sizeof styles / sizeof styles[0])) + return 1; + + if (height == 0) + return 1; + + if (width == 0) + return 1; + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + const int aheight = height / 4; + const int awidth = width / 2; + + if (aheight > w.ws_row) + { + cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; + return 1; + } + + if (awidth > w.ws_col) + { + cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; + return 1; + } + + if (xmin >= xmax) + { + cerr << "xmin must be less than xmax.\n"; + return 1; + } + + if (ymin >= ymax) + { + cerr << "ymin must be less than ymax.\n"; + return 1; + } + + const long double xscl = width / (xmax - xmin); + const long double yscl = height / (ymax - ymin); + const long double xaxis = width - (xmax * xscl); + const long double yaxis = ymax * yscl; + const int divisor = 2 * 4 * ((width / 160.0) > 1 ? (width / 160) + 1 : 1); + + setlocale(LC_CTYPE, ""); + + if (title != NULL and title[0] != '\0') + cout << wrap(title, w.ws_col) << "\n"; + + for (unsigned int i = 0; i < height; i += 4) + { + const bool ayaxis = (i <= yaxis and (i + 4) > yaxis); + const bool yaxislabel = (i <= (yaxis + 4) and (i + 4) > (yaxis + 4)); + + ostringstream ylabelstrm; + size_t ylabellength = 0; + + if (border and axislabel and axisunitslabel) + { + bool output = false; + long double label; + int adivisor = divisor; + if (i < yaxis) + adivisor = -adivisor; + + for (long double k = yaxis + adivisor; ((i < yaxis and k >= i) or (i > yaxis and k < (i + 4))) and (i >= 4 or !axislabel) and !output; k += adivisor) + { + if (i <= k and (i + 4) > k) + { + label = ymax - (k / yscl); + + output = true; + } + } + + if (output) + { + ylabellength = outputlabel(label, ylabelstrm); + ylabellength *= 2; + } + } + + for (unsigned int j = 0; j < width; j += 2) + { + const bool axaxis = (j <= xaxis and (j + 2) > xaxis); + const bool xaxislabel = (j <= (xaxis - 2) and (j + 2) > (xaxis - 2)); + + bool output = false; + + if (border) + { + if (axaxis and ayaxis) + { + cout << styles[style][6]; + output = true; + } + else if (axaxis) + { + if (axisunitslabel) + { + int adivisor = divisor; + if (i < yaxis) + adivisor = -adivisor; + + for (long double k = yaxis + adivisor; ((i < yaxis and k >= i) or (i > yaxis and k < (i + 4))) and (i >= 4 or !axislabel) and !output; k += adivisor) + { + if (i <= k and (i + 4) > k) + { + cout << styles[style][7]; + output = true; + } + } + } + if (!output) + { + if (i == 0) + cout << styles[style][4]; + else if (i >= (height - 4)) + cout << styles[style][10]; + else + cout << styles[style][1]; + output = true; + } + } + else if (ayaxis) + { + if (axisunitslabel) + { + int adivisor = divisor; + if (j < xaxis) + adivisor = -adivisor; + + for (long double k = xaxis + adivisor; ((j < xaxis and k >= j) or (j > xaxis and k < (j + 2))) and (j < (width - 4) or !axislabel) and !output; k += adivisor) + { + if (j <= k and (j + 2) > k) + { + cout << styles[style][3]; + output = true; + } + } + } + if (!output) + { + if (j == 0) + cout << styles[style][2]; + else if (j >= (width - 2)) + cout << styles[style][4]; + else + cout << styles[style][0]; + output = true; + } + } + else if (yaxislabel and xaxislabel and axisunitslabel) + { + cout << "0"; + output = true; + } + else if (j >= (width - 2) and yaxislabel and axislabel) + { + cout << "x"; + output = true; + } + else if (yaxislabel and axislabel and axisunitslabel) + { + long double label; + int adivisor = divisor; + if (j < xaxis) + { + adivisor = -adivisor; + j += 2; + } + + for (long double k = xaxis + adivisor; ((j < xaxis and k >= j) or (j > xaxis and k < (j + 2))) and j < (width - 2) and !output; k += adivisor) + { + if (j <= k and (j + 2) > k) + { + label = (k / xscl) + xmin; + + output = true; + } + } + + if (adivisor < 0) + j -= 2; + + if (output) + { + output = false; + + ostringstream strm; + size_t length = outputlabel(label, strm); + length *= 2; + if ((j >= xaxis or (j + length) < (xaxis - 4)) and (j + length) < (width - 2)) + { + cout << strm.str(); + + if (length > 2) + j += length - 2; + + if (adivisor < 0) + output = true; + else + j += 2; + } + } + } + else if (i == 0 and xaxislabel and axislabel) + { + cout << "y"; + output = true; + } + else if ((j <= (xaxis - ylabellength) and (j + 2) > (xaxis - ylabellength)) and axislabel and axisunitslabel) + { + cout << ylabelstrm.str(); + output = true; + if (ylabellength > 2) + j += ylabellength - 2; + } + } + + if (!output) + { + unsigned int dot = 0; + unsigned short color = 0; + + for (int k = 0; k < 2 and (j + k) < width; ++k) + { + for (int l = 0; l < 4 and (i + l) < height; ++l) + { + dot += (array[j + k][i + l] ? 1 : 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; + + if (color) + cout << colors[color]; + + cout << "\e[1m" << dots[dot] << "\e[22m"; + + if (color) + cout << colors[0]; + } + } + + cout << "\n"; + } + + cout << endl; + + return 0; +} + +// Convert array to graph and output +template +int graph(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const size_t rows, T **array, const graphoptions &aoptions) +{ + if (rows == 0) + return 1; + + if (array == NULL) + return 1; + + const unsigned int color = aoptions.color; + + if (color >= (sizeof colors / sizeof colors[0])) + return 1; + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + if (height == 0) + height = w.ws_row * 4; + + if (width == 0) + width = w.ws_col * 2; + + const int aheight = height / 4; + const int awidth = width / 2; + + if (aheight > w.ws_row) + { + cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; + return 1; + } + + if (awidth > w.ws_col) + { + cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; + return 1; + } + + if (xmin == 0 and xmax == 0) + { + xmin = numeric_limits::max(); + xmax = numeric_limits::min(); + + for (unsigned int i = 0; i < rows; ++i) + { + if (array[i][0] < xmin) + xmin = array[i][0]; + + if (array[i][0] > xmax) + xmax = array[i][0]; + } + } + + if (ymin == 0 and ymax == 0) + { + ymin = numeric_limits::max(); + ymax = numeric_limits::min(); + + for (unsigned int i = 0; i < rows; ++i) + { + if (array[i][1] < ymin) + ymin = array[i][1]; + + if (array[i][1] > ymax) + ymax = array[i][1]; + } + } + + if (xmin >= xmax) + { + cerr << "xmin must be less than xmax.\n"; + return 1; + } + + if (ymin >= ymax) + { + cerr << "ymin must be less than ymax.\n"; + return 1; + } + + const long double xscl = width / (xmax - xmin); + const long double yscl = height / (ymax - ymin); + const long double xaxis = width - (xmax * xscl); + const long double yaxis = ymax * yscl; + + unsigned short **aarray; + aarray = new unsigned short *[width]; + for (unsigned int i = 0; i < width; ++i) + aarray[i] = new unsigned short[height]; + + for (unsigned int i = 0; i < width; ++i) + for (unsigned int j = 0; j < height; ++j) + aarray[i][j] = 0; + + for (unsigned int i = 0; i < rows; ++i) + { + if (array[i][0] >= xmin and array[i][0] < xmax and array[i][1] >= ymin and array[i][1] < ymax) + { + const long long x = (array[i][0] * xscl) + xaxis; + const long long y = (yaxis - (array[i][1] * yscl)) - 1; + + aarray[x][y] = color + 1; + } + } + + int code = graph(height, width, xmin, xmax, ymin, ymax, aarray, aoptions); + + if (aarray != NULL) + { + for (unsigned int i = 0; i < width; ++i) + delete[] aarray[i]; + + delete[] aarray; + } + + return code; +} + +// Convert one or more functions to graph and output +template +int graph(size_t height, size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, const size_t numfunctions, T (*functions[])(T), const graphoptions &aoptions) +{ + const unsigned int color = aoptions.color; + + if (color >= (sizeof colors / sizeof colors[0])) + return 1; + + if (numfunctions == 0) + return 1; + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + if (height == 0) + height = w.ws_row * 4; + + if (width == 0) + width = w.ws_col * 2; + + const int aheight = height / 4; + const int awidth = width / 2; + + if (aheight > w.ws_row) + { + cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; + return 1; + } + + if (awidth > w.ws_col) + { + cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; + return 1; + } + + if (xmin >= xmax) + { + cerr << "xmin must be less than xmax.\n"; + return 1; + } + + if (ymin >= ymax) + { + cerr << "ymin must be less than ymax.\n"; + return 1; + } + + const size_t rows = width; + + const long double xscl = width / (xmax - xmin); + const long double yscl = height / (ymax - ymin); + const long double xaxis = width - (xmax * xscl); + const long double yaxis = ymax * yscl; + + unsigned short **array; + array = new unsigned short *[width]; + for (unsigned int i = 0; i < width; ++i) + array[i] = new unsigned short[height]; + + for (unsigned int i = 0; i < width; ++i) + for (unsigned int j = 0; j < height; ++j) + array[i][j] = 0; + + for (unsigned int j = 0; j < numfunctions; ++j) + { + unsigned short acolor = numfunctions == 1 ? color + 1 : (j % ((sizeof colors / sizeof colors[0]) - 2)) + 3; + + for (long double i = 0; i < rows; i += 0.5) + { + T x = (i / xscl) + xmin; + T y = (functions[j])(x); + + if (x >= xmin and x < xmax and y >= ymin and y < ymax) + { + const long long ax = (x * xscl) + xaxis; + const long long ay = (yaxis - (y * yscl)) - 1; + + if (array[ax][ay]) + { + if (array[ax][ay] != acolor) + array[ax][ay] = 1; + } + else + array[ax][ay] = acolor; + } + } + } + + int code = graph(height, width, xmin, xmax, ymin, ymax, array, aoptions); + + if (array != NULL) + { + for (unsigned int i = 0; i < width; ++i) + delete[] array[i]; + + delete[] array; + } + + return code; +} + +// Convert single function to function array and output +template +int graph(size_t height, size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, T afunction(T), const graphoptions &aoptions) +{ + T(*functions[]) + (T) = {afunction}; + + return graph(height, width, xmin, xmax, ymin, ymax, 1, functions, aoptions); +} diff --git a/images/ASCII graph.png b/images/ASCII graph.png new file mode 100644 index 0000000..219656d Binary files /dev/null and b/images/ASCII graph.png differ diff --git a/images/ASCII table.png b/images/ASCII table.png new file mode 100644 index 0000000..ea3edf8 Binary files /dev/null and b/images/ASCII table.png differ diff --git a/images/array to plot.png b/images/array to plot.png new file mode 100644 index 0000000..137866a Binary files /dev/null and b/images/array to plot.png differ diff --git a/images/array to table.png b/images/array to table.png new file mode 100644 index 0000000..678d81a Binary files /dev/null and b/images/array to table.png differ diff --git a/images/basic graph.png b/images/basic graph.png new file mode 100644 index 0000000..096bb6c Binary files /dev/null and b/images/basic graph.png differ diff --git a/images/basic table.png b/images/basic table.png new file mode 100644 index 0000000..b360586 Binary files /dev/null and b/images/basic table.png differ diff --git a/images/char array to table.png b/images/char array to table.png new file mode 100644 index 0000000..2deca89 Binary files /dev/null and b/images/char array to table.png differ diff --git a/images/double graph.png b/images/double graph.png new file mode 100644 index 0000000..19e0d03 Binary files /dev/null and b/images/double graph.png differ diff --git a/images/double table.png b/images/double table.png new file mode 100644 index 0000000..06eb704 Binary files /dev/null and b/images/double table.png differ diff --git a/images/function to graph.png b/images/function to graph.png new file mode 100644 index 0000000..9a934fa Binary files /dev/null and b/images/function to graph.png differ diff --git a/images/function to table.png b/images/function to table.png new file mode 100644 index 0000000..f7cb9d4 Binary files /dev/null and b/images/function to table.png differ diff --git a/images/graph colors.png b/images/graph colors.png new file mode 100644 index 0000000..f099713 Binary files /dev/null and b/images/graph colors.png differ diff --git a/images/heavy dashed graph.png b/images/heavy dashed graph.png new file mode 100644 index 0000000..b884fd7 Binary files /dev/null and b/images/heavy dashed graph.png differ diff --git a/images/heavy dashed table.png b/images/heavy dashed table.png new file mode 100644 index 0000000..5d406a6 Binary files /dev/null and b/images/heavy dashed table.png differ diff --git a/images/heavy graph.png b/images/heavy graph.png new file mode 100644 index 0000000..458abb1 Binary files /dev/null and b/images/heavy graph.png differ diff --git a/images/heavy table.png b/images/heavy table.png new file mode 100644 index 0000000..d02b477 Binary files /dev/null and b/images/heavy table.png differ diff --git a/images/light dashed graph.png b/images/light dashed graph.png new file mode 100644 index 0000000..e540fa0 Binary files /dev/null and b/images/light dashed graph.png differ diff --git a/images/light dashed table.png b/images/light dashed table.png new file mode 100644 index 0000000..ed10789 Binary files /dev/null and b/images/light dashed table.png differ diff --git a/images/light graph.png b/images/light graph.png new file mode 100644 index 0000000..669a529 Binary files /dev/null and b/images/light graph.png differ diff --git a/images/light table.png b/images/light table.png new file mode 100644 index 0000000..c097dbe Binary files /dev/null and b/images/light table.png differ diff --git a/images/multiple functions to graph.png b/images/multiple functions to graph.png new file mode 100644 index 0000000..b998860 Binary files /dev/null and b/images/multiple functions to graph.png differ diff --git a/images/multiple functions to table.png b/images/multiple functions to table.png new file mode 100644 index 0000000..a139a87 Binary files /dev/null and b/images/multiple functions to table.png differ diff --git a/images/plot colors.png b/images/plot colors.png new file mode 100644 index 0000000..17188de Binary files /dev/null and b/images/plot colors.png differ diff --git a/images/sorted array to table.png b/images/sorted array to table.png new file mode 100644 index 0000000..ed79a46 Binary files /dev/null and b/images/sorted array to table.png differ diff --git a/tables.cpp b/tables.cpp new file mode 100644 index 0000000..48a69d2 --- /dev/null +++ b/tables.cpp @@ -0,0 +1,329 @@ +// Teal Dulcet, CS546 + +// Compile: g++ -Wall -g -O3 tables.cpp -o tables + +// Run: ./tables + +#include +// #include +#include +#include +#include "tables.hpp" + +using namespace std; + +long double afunction(long double x) +{ + return x + 1; +} + +long double function1(long double x) +{ + return 2 * x; +} + +long double function2(long double x) +{ + return pow(x, 2); +} + +int dimensions = 0; +int sortdimension = 0; + +/* template +int compare(const void *pa, const void *pb) +{ + const T *a = *(const T **)pa; + const T *b = *(const T **)pb; + + if (a[sortdimension] == b[sortdimension]) + { + for (int i = 0; i < dimensions; ++i) + { + if (sortdimension != i and a[i] != b[i]) + { + if (a[i] > b[i]) + return 1; + + return -1; + } + } + } + + if (a[sortdimension] > b[sortdimension]) + return 1; + else if (a[sortdimension] == b[sortdimension]) + return 0; + + return -1; +} */ + +template +bool compare(const T *a, const T *b) +{ + if (a[sortdimension] == b[sortdimension]) + for (int i = 0; i < dimensions; ++i) + if (sortdimension != i and a[i] != b[i]) + return a[i] < b[i]; + + return a[sortdimension] < b[sortdimension]; +} + +int main() +{ + const size_t rows = 5; + const size_t columns = 5; + + const long double xmin = -10; + const long double xmax = 10; + const long double xscl = 2; // 80 / (xmax - xmin); + + // Output array as table + cout << "\nOutput array as table\n\n"; + { + long long **array; + array = new long long *[rows]; + for (unsigned int i = 0; i < rows; ++i) + array[i] = new long long[columns]; + + for (unsigned int i = 0; i < rows; ++i) + for (unsigned int j = 0; j < columns; ++j) + array[i][j] = rand(); + + tableoptions aoptions; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + table(rows, columns, array, NULL, NULL, aoptions); + } + + if (array != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + delete[] array[i]; + + delete[] array; + } + } + { + long double **array; + array = new long double *[rows]; + for (unsigned int i = 0; i < rows; ++i) + array[i] = new long double[columns]; + + for (unsigned int i = 0; i < rows; ++i) + for (unsigned int j = 0; j < columns; ++j) + array[i][j] = static_cast(rand()) / static_cast(RAND_MAX); + + tableoptions aoptions; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + table(rows, columns, array, NULL, NULL, aoptions); + } + + if (array != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + delete[] array[i]; + + delete[] array; + } + } + // Output char array as table + cout << "\nOutput char array as table\n\n"; + { + const char *const aarray[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; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + 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"; + { + const size_t rows = 4; + const size_t columns = 4; + + const char *const aarray[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"}, + {"Data 13", "Data 14", "Data 15", "Data 16"}}; + + 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; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + 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; + array = new bool *[rows]; + for (unsigned int i = 0; i < rows; ++i) + array[i] = new bool[columns]; + + for (unsigned int i = 0; i < rows; ++i) + for (unsigned int j = 0; j < columns; ++j) + array[i][j] = rand() % 2; + + tableoptions aoptions; + aoptions.boolalpha = true; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + table(rows, columns, array, NULL, NULL, aoptions); + } + + if (array != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + delete[] array[i]; + + delete[] array; + } + } + // Output sorted array as table + cout << "\nOutput sorted array as table\n\n"; + { + int **array; + array = new int *[rows]; + for (unsigned int i = 0; i < rows; ++i) + array[i] = new int[columns]; + + for (unsigned int i = 0; i < rows; ++i) + for (unsigned int j = 0; j < columns; ++j) + array[i][j] = rand(); + + dimensions = columns; + sortdimension = 0; + + // qsort(array, rows, sizeof(array[0]), compare); + sort(array, array + rows, compare); + + tableoptions aoptions; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + table(rows, columns, array, NULL, NULL, aoptions); + } + + if (array != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + delete[] array[i]; + + delete[] array; + } + } + // Output single function as table + cout << "\nOutput single function as table\n\n"; + { + tableoptions aoptions; + aoptions.headerrow = true; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + table(xmin, xmax, xscl, afunction, aoptions); + } + } + // Output multiple functions as table + cout << "\nOutput multiple functions as table\n\n"; + { + long double (*functions[])(long double) = {function1, function2}; + + tableoptions aoptions; + aoptions.headerrow = true; + + for (unsigned int k = 0; k < (sizeof styles / sizeof styles[0]); ++k) + { + aoptions.style = k; + + table(xmin, xmax, xscl, 2, functions, aoptions); + } + } + + return 0; +} diff --git a/tables.hpp b/tables.hpp new file mode 100644 index 0000000..92e1056 --- /dev/null +++ b/tables.hpp @@ -0,0 +1,519 @@ +// Teal Dulcet, CS546 + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +const char *const styles[][11] = { + {"-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, //ASCII + {"—", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, //Basic + {"─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, //Light + {"━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"}, //Heavy + {"═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"}, //Double + {"╌", "╎", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, //Light Dashed + {"╍", "╏", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"} //Heavy Dashed +}; +// {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}};//No border + +struct tableoptions +{ + bool headerrow; + bool headercolumn; + bool tableborder; + bool cellborder; + unsigned int padding; + ios_base &(*alignment)(ios_base &); + bool boolalpha; + char *title; + unsigned int style; + tableoptions(void); + ~tableoptions(void); +}; + +tableoptions::tableoptions(void) +{ + headerrow = false; + headercolumn = false; + tableborder = true; + cellborder = false; + padding = 1; + alignment = left; + boolalpha = false; + title = NULL; + style = 2; +} + +tableoptions::~tableoptions(void) +{ +} + +const tableoptions tabledefaultoptions; + +// Number of columns needed to represent the string +// Adapted from: https://stackoverflow.com/a/31124065 +int strcol(const char *const str) +{ + size_t length = strlen(str); + for (size_t i = 0; i < length; ++i) + if (iscntrl(str[i])) + { + cerr << "\nError! Control character in string.\n"; + cout << "Control character: " << (int)str[i] << "\n"; + } + + length = mbstowcs(NULL, str, 0); + if (length == (size_t)-1) + { + cerr << "\nError! mbstowcs failed. Invalid multibyte character.\n"; + exit(1); + } + ++length; + + wchar_t *wcstring = new wchar_t[length]; + + if (mbstowcs(wcstring, str, length) == (size_t)-1) + { + if (wcstring != NULL) + delete[] wcstring; + + cerr << "\nError! mbstowcs failed. Invalid multibyte character.\n"; + exit(1); + } + + int width = wcswidth(wcstring, length); + if (width == -1) + { + cerr << "\nError! wcswidth failed. Nonprintable wide character.\n"; + exit(1); + } + + if (wcstring != NULL) + delete[] wcstring; + + return width; +} + +// Word wrap +// Source: https://gist.github.com/tdulcet/819821ca69501822ad3f84a060c640a0 +// Adapted from: https://stackoverflow.com/a/42016346 and https://stackoverflow.com/a/13094734 +string wrap(const char *const str, const size_t line_length) +{ + char words[strlen(str) + 1]; + strcpy(words, str); + string wrapped; + + size_t index = 0; + size_t linelen = 0; + while (words[index] != '\0') + { + if (words[index] == '\n') + { + linelen = 0; + } + else if (isspace(words[index])) + { + size_t tempindex = index + 1; + size_t templinelen = linelen; + while (!isspace(words[tempindex]) and words[tempindex] != '\0') + { + ++templinelen; + + ++tempindex; + } + + char temp[templinelen + 1]; + strncpy(temp, words + (index - linelen), templinelen); + temp[templinelen] = '\0'; + + size_t width = strcol(temp); + + if (width >= line_length) + { + words[index] = '\n'; + linelen = 0; + } + } + + if (words[index] == '\t') + linelen += 8 - (linelen % 8); + else if (words[index] != '\n') + ++linelen; + + ++index; + } + wrapped = words; + return wrapped; +} + +// Output char array as table +int table(const size_t rows, const size_t columns, char ***array, const tableoptions &aoptions) +{ + if (array == NULL) + return 1; + + const bool headerrow = aoptions.headerrow; + const bool headercolumn = aoptions.headercolumn; + const bool tableborder = aoptions.tableborder; + const bool cellborder = aoptions.cellborder; + const unsigned int padding = aoptions.padding; + const char *const title = aoptions.title; + const unsigned int style = aoptions.style; + + if (style >= (sizeof styles / sizeof styles[0])) + return 1; + + int columnwidth[columns]; + for (unsigned int j = 0; j < columns; ++j) + columnwidth[j] = 0; + + int width = 0; + + setlocale(LC_CTYPE, ""); + + for (unsigned int j = 0; j < columns; ++j) + { + for (unsigned int i = 0; i < rows; ++i) + { + int cellwidth = strcol(array[i][j]); + if (cellwidth > columnwidth[j]) + columnwidth[j] = cellwidth; + } + + width += columnwidth[j]; + } + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + if (tableborder and (cellborder or headerrow or headercolumn)) + width += (((2 * padding) + 1) * columns) + 1; + else + width += ((2 * padding) * columns) + 2; + + if (width > w.ws_col) + { + cerr << "The width of the table (" << width << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; + return 1; + } + + if (title != NULL and title[0] != '\0') + cout << wrap(title, w.ws_col) << "\n"; + + if (tableborder) + { + cout << styles[style][2]; + + for (unsigned int j = 0; j < columns; ++j) + { + for (unsigned int k = 0; k < (2 * padding) + columnwidth[j]; ++k) + cout << styles[style][0]; + + if (j == (columns - 1)) + cout << styles[style][4] << "\n"; + else if (cellborder or headerrow or (j == 0 and headercolumn)) + cout << styles[style][3]; + else + cout << styles[style][0]; + } + } + + for (unsigned int i = 0; i < rows; ++i) + { + for (unsigned int j = 0; j < columns; ++j) + { + if (tableborder) + { + if (j == 0 or cellborder or (i == 0 and headerrow) or (j == 1 and headercolumn)) + cout << styles[style][1]; + else + cout << " "; + } + + if ((i == 0 and headerrow) or (j == 0 and headercolumn)) + { + int difference = columnwidth[j] - strcol(array[i][j]); + int apadding = (difference / 2); + + for (unsigned int k = 0; k < padding + apadding; ++k) + cout << " "; + + cout << "\e[1m" << array[i][j] << "\e[22m"; + + for (unsigned int k = 0; k < padding + (difference - apadding); ++k) + cout << " "; + } + else + { + for (unsigned int k = 0; k < padding; ++k) + cout << " "; + + cout << aoptions.alignment << setw(columnwidth[j]) << array[i][j]; + + for (unsigned int k = 0; k < padding; ++k) + cout << " "; + } + } + + if (tableborder) + cout << styles[style][1]; + + cout << "\n"; + + if (tableborder) + { + if (i == (rows - 1)) + cout << styles[style][8]; + else if (cellborder or (i == 0 and headerrow) or headercolumn) + cout << styles[style][5]; + + for (unsigned int j = 0; j < columns; ++j) + { + if (cellborder or i == (rows - 1) or (i == 0 and headerrow) or (j == 0 and headercolumn)) + for (unsigned int k = 0; k < (2 * padding) + columnwidth[j]; ++k) + cout << styles[style][0]; + else if (headercolumn) + for (unsigned int k = 0; k < (2 * padding) + columnwidth[j]; ++k) + cout << " "; + + if (j == (columns - 1)) + { + if (i == (rows - 1)) + cout << styles[style][10]; + else if (cellborder or (i == 0 and headerrow)) + cout << styles[style][7]; + else if (headercolumn) + cout << styles[style][1]; + + if (cellborder or (i == 0 and headerrow) or headercolumn) + cout << "\n"; + } + else + { + if (i == (rows - 1)) + { + if (cellborder or (j == 0 and headercolumn)) + cout << styles[style][9]; + else + cout << styles[style][0]; + } + else if (cellborder or ((i == 0 and headerrow) and (j == 0 and headercolumn))) + cout << styles[style][6]; + else if (i == 0 and headerrow) + cout << styles[style][9]; + else if (headercolumn) + { + if (j == 0) + cout << styles[style][7]; + else + cout << " "; + } + } + } + } + } + + cout << endl; + + return 0; +} + +// 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) +{ + if (array == NULL) + return 1; + + unsigned int i = 0; + unsigned int j = 0; + + if (headerrow != NULL) + ++rows; + + if (headercolumn != NULL) + ++columns; + + char ***aarray; + aarray = new char **[rows]; + for (unsigned int i = 0; i < rows; ++i) + aarray[i] = new char *[columns]; + + if (headerrow != NULL) + { + for (unsigned int j = 0; j < columns; ++j) + { + aarray[i][j] = new char[strlen(headerrow[j]) + 1]; + strcpy(aarray[i][j], headerrow[j]); + } + + ++i; + } + + for (unsigned int ii = 0; i < rows; ++i) + { + if (headercolumn != NULL) + { + unsigned int ii = i; + + if (headerrow != NULL) + --ii; + + aarray[i][j] = new char[strlen(headercolumn[ii]) + 1]; + strcpy(aarray[i][j], headercolumn[ii]); + + ++j; + } + + for (unsigned int jj = 0; j < columns; ++j) + { + ostringstream strm; + + if (aoptions.boolalpha) + strm << boolalpha; + + strm << array[ii][jj]; + string str = strm.str(); + aarray[i][j] = new char[str.length() + 1]; + strcpy(aarray[i][j], str.c_str()); + + ++jj; + } + + j = 0; + ++ii; + } + + int code = table(rows, columns, aarray, aoptions); + + if (aarray != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + { + for (unsigned int j = 0; j < columns; ++j) + delete[] aarray[i][j]; + + delete[] aarray[i]; + } + delete[] aarray; + } + + return code; +} + +// Convert one or more functions to array and output as table +template +int table(const long double xmin, const long double xmax, const long double xscl, const size_t numfunctions, T (*functions[])(T), const tableoptions &aoptions) +{ + if (numfunctions == 0) + return 1; + + if (xmin >= xmax) + { + cerr << "xmin must be less than xmax.\n"; + return 1; + } + + if (xscl <= 0) + { + cerr << "xscl must be greater than zero.\n"; + return 1; + } + + const size_t rows = ((xmax - xmin) * xscl) + 1; + const size_t columns = numfunctions + 1; + + const char *const aheaderrow[] = {"x", "y"}; + // const char* const aheaderrow[] = {"", "x", "y"}; + + char **headerrow = NULL; + headerrow = new char *[columns]; + + for (unsigned int j = 0; j < columns; ++j) + { + const size_t length = (sizeof aheaderrow / sizeof aheaderrow[0]); + + if (j < (length - 1) or numfunctions == 1) + { + headerrow[j] = new char[strlen(aheaderrow[j]) + 1]; + strcpy(headerrow[j], aheaderrow[j]); + } + else + { + ostringstream strm; + strm << aheaderrow[length - 1] << j - length + 2; + string str = strm.str(); + headerrow[j] = new char[str.length() + 1]; + strcpy(headerrow[j], str.c_str()); + } + } + + char **headercolumn = NULL; + // headercolumn = new char *[rows + 1]; + + // for (unsigned int i = 0; i < rows + 1; ++i) + // { + // ostringstream strm; + // strm << i + 1; + // string str = strm.str(); + // headercolumn[i] = new char[str.length() + 1]; + // strcpy(headercolumn[i], str.c_str()); + // } + + T **array; + array = new T *[rows]; + for (unsigned int i = 0; i < rows; ++i) + array[i] = new T[columns]; + + for (unsigned int i = 0; i < rows; ++i) + { + array[i][0] = (i / xscl) + xmin; + + for (unsigned int j = 0; j < numfunctions; ++j) + array[i][j + 1] = (functions[j])(array[i][0]); + } + + int code = table(rows, columns, array, headerrow, headercolumn, aoptions); + + if (array != NULL) + { + for (unsigned int i = 0; i < rows; ++i) + delete[] array[i]; + + delete[] array; + } + + if (headerrow != NULL) + { + for (unsigned int j = 0; j < columns; ++j) + delete[] headerrow[j]; + + delete[] headerrow; + } + + // if (headercolumn != NULL) + // { + // for (unsigned int i = 0; i < rows + 1; ++i) + // delete[] headercolumn[i]; + + // delete[] headercolumn; + // } + + return code; +} + +// Convert single function to array and output as table +template +int table(const long double xmin, const long double xmax, const long double xscl, T afunction(T), const tableoptions &aoptions) +{ + T(*functions[]) + (T) = {afunction}; + + return table(xmin, xmax, xscl, 1, functions, aoptions); +}