diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07c76ea..f564d2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: python3 -m pip install --upgrade pip python3 -m pip install ruff - name: Script - run: ruff --format=github --target-version py37 --select F,E,W,I,D,UP,YTT,S,BLE,B,A,C4,T10,EM,EXE,ISC,ICN,G,PIE,PYI,Q,RSE,RET,SLF,SIM,TID,TCH,ARG,PGH,PL,TRY,FLY,RUF --ignore E101,E501,W191,D211,D213,D401,PLR09,PLR2004,RUF003 . + run: ruff --format=github --target-version py37 --select F,E,W,I,D,UP,YTT,S,BLE,B,A,C4,T10,EM,EXE,ISC,ICN,G,PIE,PYI,Q,RSE,RET,SLF,SLOT,SIM,TID,TCH,ARG,PGH,PL,PLC1901,TRY,FLY,PERF,RUF,RUF017 --ignore E101,E501,W191,D211,D213,D401,PLR09,PLR2004,RUF001,RUF002,RUF003 . continue-on-error: true Python: @@ -75,7 +75,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/README.md b/README.md index bbe6d97..78d3e6a 100644 --- a/README.md +++ b/README.md @@ -500,9 +500,10 @@ Values: ![](images/heavy%20table.png) 4. `style_double`: Double ![](images/double%20table.png) -5. `style_light_dashed`: Light Dashed +5. `style_arc`: Light Arc +6. `style_light_dashed`: Light Dashed ![](images/light%20dashed%20table.png) -6. `style_heavy_dashed`: Heavy Dashed +7. `style_heavy_dashed`: Heavy Dashed ![](images/heavy%20dashed%20table.png) #### Check size @@ -534,6 +535,70 @@ 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 histogram + +##### C style pointer + +```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 = 100; + + double *array; // array can be any data type + + // Allocate and set array + + graphs::histogram(height, width, xmin, xmax, ymin, ymax, rows, array); + + // Deallocate array + + return 0; +} +``` + +##### C++ array/vector + +```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 = 100; + + vector array(rows); // array can be any data type + + // Set array + + graphs::histogram(height, width, xmin, xmax, ymin, ymax, 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 resulting histogram. + #### Output single array as plot ##### C style pointer @@ -559,7 +624,7 @@ int main() // Allocate and set array - graphs::array(height, width, xmin, xmax, ymin, ymax, rows, array); + graphs::plot(height, width, xmin, xmax, ymin, ymax, rows, array); // Deallocate array @@ -590,7 +655,7 @@ int main() // Set array - graphs::array(height, width, xmin, xmax, ymin, ymax, array); + graphs::plot(height, width, xmin, xmax, ymin, ymax, array); return 0; } @@ -600,7 +665,7 @@ If `xmin` and `xmax` are both `0`, they will be set to the respective minimum an ![](images/array%20to%20plot.png) -Use `graphs::arrays()` to plot multiple arrays, which can be of different sizes. +Use `graphs::plots()` to plot multiple arrays, which can be of different sizes. #### Output single function as graph @@ -789,6 +854,27 @@ Formats 2-5 are similar to the respective `--to` options with the [numfmt](https Option: `yunits`\ Values: Same as above. +#### Type + +Option: `type`\ +Values: + +1. `type_braille`: Braille (default) +2. `type_block`: Block + +The Braille type has the highest resolution of 2×4 pixels per character, while the block type uses 2×2. This option is only used for plots and graphs. Histograms use 1×8 pixels per character. + +#### Mark type + +Option: `mark`\ +Values: + +1. `mark_dot`: Dot (default) +2. `mark_plus`: Plus +3. `mark_square`: Square + +The dot mark type uses a single pixel per mark, the plus uses five pixels and the square uses eight pixels. This option is only used for plots and graphs. + #### Title Option: `title`\ @@ -811,9 +897,10 @@ Values: ![](images/heavy%20graph.png) 4. `style_double`: Double ![](images/double%20graph.png) -5. `style_light_dashed`: Light Dashed +5. `style_arc`: Light Arc +6. `style_light_dashed`: Light Dashed ![](images/light%20dashed%20graph.png) -6. `style_heavy_dashed`: Heavy Dashed +7. `style_heavy_dashed`: Heavy Dashed ![](images/heavy%20dashed%20graph.png) #### Graph/Plot Color diff --git a/graphs.cpp b/graphs.cpp index 269e3fa..9cd50c6 100644 --- a/graphs.cpp +++ b/graphs.cpp @@ -1,12 +1,13 @@ // Teal Dulcet, CS546 -// Compile: g++ -Wall -g -O3 -std=c++14 graphs.cpp -o graphs +// Compile: g++ -std=c++14 -Wall -g -O3 graphs.cpp -o graphs // Run: ./graphs #include #include #include +#include #include "graphs.hpp" using namespace std; @@ -54,30 +55,99 @@ int main() const size_t rows = 10; const size_t columns = 2; + // Output array as histogram + cout << "\nOutput array as histogram\n\n"; + { + const size_t rows = 100; + + default_random_engine generator; + normal_distribution distribution(0, 1); + + { + long double *array; + array = new long double[rows]; + + for (unsigned i = 0; i < rows; ++i) + array[i] = distribution(generator); + + graphs::options aoptions; + + for (const graphs::style_type style : graphs::style_types) + { + aoptions.style = style; + + graphs::histogram(height, width, xmin, xmax, ymin, ymax, rows, array, aoptions); + } + + if (array) + delete[] array; + } + { + array aarray; + + for (unsigned i = 0; i < rows; ++i) + aarray[i] = distribution(generator); + + graphs::options aoptions; + + for (const graphs::style_type style : graphs::style_types) + { + aoptions.style = style; + + graphs::histogram(height, width, xmin, xmax, ymin, ymax, aarray, aoptions); + } + } + { + vector array(rows); + + for (unsigned i = 0; i < rows; ++i) + array[i] = distribution(generator); + + graphs::options aoptions; + + for (const graphs::style_type style : graphs::style_types) + { + aoptions.style = style; + + graphs::histogram(height, width, xmin, xmax, ymin, ymax, array, aoptions); + } + } + } + // Output single 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) + for (unsigned 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) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = i + j; // rand(); graphs::options aoptions; - for (const graphs::style_type style : graphs::style_types) + for (const graphs::type_type type : graphs::type_types) { - aoptions.style = style; + aoptions.type = type; - graphs::array(height, width, xmin, xmax, ymin, ymax, rows, array, aoptions); + for (const graphs::mark_type mark : graphs::mark_types) + { + aoptions.mark = mark; + + for (const graphs::style_type style : graphs::style_types) + { + aoptions.style = style; + + graphs::plot(height, width, xmin, xmax, ymin, ymax, rows, array, aoptions); + } + } } if (array) { - for (unsigned int i = 0; i < rows; ++i) + for (unsigned i = 0; i < rows; ++i) delete[] array[i]; delete[] array; @@ -86,33 +156,53 @@ int main() { array, rows> aarray; - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) aarray[i][j] = i + j; // rand(); graphs::options aoptions; - for (const graphs::style_type style : graphs::style_types) + for (const graphs::type_type type : graphs::type_types) { - aoptions.style = style; + aoptions.type = type; - graphs::array(height, width, xmin, xmax, ymin, ymax, aarray, aoptions); + for (const graphs::mark_type mark : graphs::mark_types) + { + aoptions.mark = mark; + + for (const graphs::style_type style : graphs::style_types) + { + aoptions.style = style; + + graphs::plot(height, width, xmin, xmax, ymin, ymax, aarray, aoptions); + } + } } } { vector> array(rows, vector(columns)); - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = i + j; // rand(); graphs::options aoptions; - for (const graphs::style_type style : graphs::style_types) + for (const graphs::type_type type : graphs::type_types) { - aoptions.style = style; + aoptions.type = type; - graphs::array(height, width, xmin, xmax, ymin, ymax, array, aoptions); + for (const graphs::mark_type mark : graphs::mark_types) + { + aoptions.mark = mark; + + for (const graphs::style_type style : graphs::style_types) + { + aoptions.style = style; + + graphs::plot(height, width, xmin, xmax, ymin, ymax, array, aoptions); + } + } } } // Output single function as graph @@ -190,7 +280,7 @@ int main() /* aoptions.style = 2; - for (unsigned int k = 10; k < 300; ++k) + for (unsigned k = 10; k < 300; ++k) { cout << "\e[1;1H" << "\e[2J"; diff --git a/graphs.hpp b/graphs.hpp index 29402ff..c9320e2 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -30,11 +30,12 @@ namespace graphs style_light, style_heavy, style_double, + style_arc, style_light_dashed, style_heavy_dashed }; - enum style_type const style_types[] = {style_ASCII, style_basic, style_light, style_heavy, style_double, style_light_dashed, style_heavy_dashed}; + enum style_type const style_types[] = {style_ASCII, style_basic, style_light, style_heavy, style_double, style_arc, style_light_dashed, style_heavy_dashed}; const char *const styles[][11] = { {"-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, // ASCII @@ -42,6 +43,7 @@ namespace graphs {"─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, // Light {"━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"}, // Heavy {"═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"}, // Double + {"─", "│", "╭", "┬", "╮", "├", "┼", "┤", "╰", "┴", "╯"}, // Light Arc {"╌", "┊", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, // Light Dashed {"╍", "┋", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"} // Heavy Dashed // {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "} // No border @@ -73,8 +75,49 @@ namespace graphs 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 dotvalues[][4] = {{0x1, 0x2, 0x4, 0x40}, {0x8, 0x10, 0x20, 0x80}}; - const int values[][4] = {{0x1, 0x2, 0x4, 0x40}, {0x8, 0x10, 0x20, 0x80}}; + const char *const blocks[] = {" ", "▖", "▗", "▄", "▘", "▌", "▚", "▙", "▝", "▞", "▐", "▟", "▀", "▛", "▜", "█"}; + const int blockvalues[][2] = {{4, 1}, {8, 2}}; + + const char *const bars[] = {" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; + + enum type_type + { + type_braille, + type_block, + type_histogram // Set automatically by the histogram() function + }; + + enum type_type const type_types[] = {type_braille, type_block /* , type_histogram */}; + + enum plot_type + { + plot_scatter, + plot_line + }; + + enum plot_type const plot_types[] = {plot_scatter, plot_line}; + + const short marks[][8][2] = {{{0, 0}}, {{0, 1}, {-1, 0}, {0, 0}, {1, 0}, {0, -1}}, {{-1, 1}, {0, 1}, {1, 1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}}}; + + enum mark_type + { + mark_dot, + mark_plus, + mark_square + }; + + enum mark_type const mark_types[] = {mark_dot, mark_plus, mark_square}; + + enum graph_type + { + graph_dot, + graph_shade_above, + graph_shade_below + }; + + enum graph_type const graph_types[] = {graph_dot, graph_shade_above, graph_shade_below}; 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}; @@ -112,6 +155,8 @@ namespace graphs bool axisunitslabel = true; units_type xunits = units_fracts; units_type yunits = units_fracts; + type_type type = type_braille; + mark_type mark = mark_dot; const char *title = nullptr; style_type style = style_light; color_type color = color_red; @@ -119,6 +164,7 @@ namespace graphs }; const options defaultoptions; + options histogram_defaultoptions; template constexpr size_t size(const T &array) @@ -412,6 +458,7 @@ namespace graphs const bool axislabel = aoptions.axislabel; const bool axistick = aoptions.axistick; const bool axisunitslabel = aoptions.axisunitslabel; + const type_type type = aoptions.type; const char *const title = aoptions.title; const style_type style = aoptions.style; @@ -424,8 +471,12 @@ namespace graphs struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - const size_t aheight = height / 4; - const size_t awidth = width / 2; + const size_t ai = type == type_histogram ? 8 : type == type_block ? 2 + : 4; + const size_t aj = type == type_histogram ? 1 : 2; + + const size_t aheight = height / ai; + const size_t awidth = width / aj; if (aoptions.check) { @@ -460,8 +511,8 @@ namespace graphs : width - (xmax / xstep); const long double yaxis = ymin > 0 ? height : ymax < 0 ? 0 : ymax / ystep; - const int xdivisor = 2 * 4 * ((width / 160) + 2); - const int ydivisor = 2 * 4 * ((height / 160) + 2); + const int xdivisor = 4 * aj * ((((2 * width) / aj) / 160) + 2); + const int ydivisor = 2 * ai * ((((4 * height) / ai) / 160) + 2); setlocale(LC_ALL, ""); @@ -478,10 +529,10 @@ namespace graphs cout << styles[style][4] << '\n'; } - for (size_t i = 0; i < height; i += 4) + for (size_t i = 0; i < height; i += ai) { - const bool ayaxis = yaxis <= (height - 4) ? i <= yaxis and (i + 4) > yaxis : i < yaxis and (i + 4) >= yaxis; - const bool yaxislabel = yaxis <= (height - 4) ? i <= (yaxis + 4) and (i + 4) > (yaxis + 4) : i < (yaxis - 4) and (i + 4) >= (yaxis - 4); + const bool ayaxis = yaxis <= (height - ai) ? i <= yaxis and (i + ai) > yaxis : i < yaxis and (i + ai) >= yaxis; + const bool yaxislabel = yaxis <= (height - ai) ? i <= (yaxis + ai) and (i + ai) > (yaxis + ai) : i < (yaxis - ai) and (i + ai) >= (yaxis - ai); ostringstream ylabelstrm; size_t ylabellength = 0; @@ -492,9 +543,9 @@ namespace graphs long double label = 0; int adivisor = i < yaxis ? -ydivisor : ydivisor; - for (long double k = yaxis + adivisor; (i < yaxis ? k >= i : k < (i + 4)) and i >= 4 and !output; k += adivisor) + for (long double k = yaxis + adivisor; (i < yaxis ? k >= i : k < (i + ai)) and i >= ai and !output; k += adivisor) { - if (i <= k and (i + 4) > k) + if (i <= k and (i + ai) > k) { label = ymax - ((k > height ? height : k) * ystep); @@ -505,17 +556,17 @@ namespace graphs if (output) { ylabellength = outputlabel(label, aoptions.yunits, ylabelstrm); - ylabellength *= 2; + ylabellength *= aj; } } if (border) cout << styles[style][1]; - for (size_t j = 0; j < width; j += 2) + for (size_t j = 0; j < width; j += aj) { - const bool axaxis = xaxis >= 2 ? j < xaxis and (j + 2) >= xaxis : j <= xaxis and (j + 2) > xaxis; - const bool xaxislabel = xaxis >= 2 ? j < (xaxis - 2) and (j + 2) >= (xaxis - 2) : j <= (xaxis + 2) and (j + 2) > (xaxis + 2); + const bool axaxis = xaxis >= aj ? j < xaxis and (j + aj) >= xaxis : j <= xaxis and (j + aj) > xaxis; + const bool xaxislabel = xaxis >= aj ? j < (xaxis - aj) and (j + aj) >= (xaxis - aj) : j <= (xaxis + aj) and (j + aj) > (xaxis + aj); bool output = false; @@ -533,7 +584,7 @@ namespace graphs cout << styles[style][4]; output = true; } - else if (i >= (height - 4)) + else if (i >= (height - ai)) { cout << styles[style][10]; output = true; @@ -542,11 +593,11 @@ namespace graphs { int adivisor = i < yaxis ? -ydivisor : ydivisor; - for (long double k = yaxis + adivisor; (i < yaxis ? k >= i : k < (i + 4)) and i >= 4 and !output; k += adivisor) + for (long double k = yaxis + adivisor; (i < yaxis ? k >= i : k < (i + ai)) and i >= ai and !output; k += adivisor) { - if (i <= k and (i + 4) > k) + if (i <= k and (i + ai) > k) { - cout << styles[style][xaxis >= 2 ? 7 : 5]; + cout << styles[style][xaxis >= aj ? 7 : 5]; output = true; } } @@ -564,7 +615,7 @@ namespace graphs cout << styles[style][2]; output = true; } - else if (j >= (width - 2)) + else if (j >= (width - aj)) { cout << styles[style][4]; output = true; @@ -573,11 +624,11 @@ namespace graphs { int adivisor = j < xaxis ? -xdivisor : xdivisor; - for (long double k = xaxis + adivisor; (j < xaxis ? k >= j : k < (j + 2)) and j < (width - (2 * 2)) and !output; k += adivisor) + for (long double k = xaxis + adivisor; (j < xaxis ? k >= j : k < (j + aj)) and j < (width - (aj * 2)) and !output; k += adivisor) { - if (j <= k and (j + 2) > k) + if (j <= k and (j + aj) > k) { - cout << styles[style][yaxis <= (height - 4) ? 3 : 9]; + cout << styles[style][yaxis <= (height - ai) ? 3 : 9]; output = true; } } @@ -593,7 +644,7 @@ namespace graphs cout << "0"; output = true; } - else if ((xaxis <= (width - 2) ? j >= (width - 2) : j == 0) and yaxislabel and axislabel) + else if ((xaxis <= (width - aj) ? j >= (width - aj) : j == 0) and yaxislabel and axislabel) { cout << "x"; output = true; @@ -603,11 +654,11 @@ namespace graphs long double label = 0; int adivisor = j < xaxis ? -xdivisor : xdivisor; if (j < xaxis) - j += 2; + j += aj; - for (long double k = xaxis + adivisor; (j < xaxis ? k >= j : k < (j + 2)) and j < (width - 2) and !output; k += adivisor) + for (long double k = xaxis + adivisor; (j < xaxis ? k >= j : k < (j + aj)) and j < (width - aj) and !output; k += adivisor) { - if (j <= k and (j + 2) > k) + if (j <= k and (j + aj) > k) { label = ((k > width ? width : k) * xstep) + xmin; @@ -616,7 +667,7 @@ namespace graphs } if (adivisor < 0) - j -= 2; + j -= aj; if (output) { @@ -624,32 +675,32 @@ namespace graphs ostringstream strm; size_t length = outputlabel(label, aoptions.xunits, strm); - length *= 2; - if ((j >= xaxis or (j + length) < (ymin <= 0 and ymax >= 0 and xmin <= 0 and xmax >= 0 ? xaxis - 4 : xaxis)) and (j + length) < (width - 2) and (xaxis <= (width - 2) or j > 2)) + length *= aj; + if ((j >= xaxis or (j + length) < (ymin <= 0 and ymax >= 0 and xmin <= 0 and xmax >= 0 ? xaxis - ai : xaxis)) and (j + length) < (width - aj) and (xaxis <= (width - aj) or j > aj)) { cout << strm.str(); - if (length > 2) - j += length - 2; + if (length > aj) + j += length - aj; if (adivisor < 0) output = true; else - j += 2; + j += aj; } } } - else if ((yaxis >= 4 ? i == 0 : i >= (height - 4)) and xaxislabel and axislabel) + else if ((yaxis >= ai ? i == 0 : i >= (height - ai)) and xaxislabel and axislabel) { cout << "y"; output = true; } - else if (ylabellength and (xaxis < 2 ? xaxislabel : j < (xaxis - ylabellength) and (j + 2) >= (xaxis - ylabellength)) and (yaxis >= 4 or i < (height - 4)) and axistick and axisunitslabel) + else if (ylabellength and (xaxis < aj ? xaxislabel : j < (xaxis - ylabellength) and (j + aj) >= (xaxis - ylabellength)) and (yaxis >= ai or i < (height - ai)) and axistick and axisunitslabel) { cout << ylabelstrm.str(); output = true; - if (ylabellength > 2) - j += ylabellength - 2; + if (ylabellength > aj) + j += ylabellength - aj; } } @@ -658,13 +709,23 @@ namespace graphs size_t dot = 0; unsigned short color = 0; - for (size_t k = 0; k < 2 and k < (width - j); ++k) + for (size_t k = 0; k < aj and k < (width - j); ++k) { - for (size_t l = 0; l < 4 and l < (height - i); ++l) + for (size_t l = 0; l < ai and l < (height - i); ++l) { const unsigned short value = array[j + k][i + l]; if (value) - dot += values[k][l]; + { + if (type == type_histogram) + { + if (!dot) + dot = (graphs::size(bars) - l) - 1; + } + else if (type == type_block) + dot += blockvalues[k][l]; + else + dot += dotvalues[k][l]; + } if (color) { if (value and color != value) @@ -681,7 +742,8 @@ namespace graphs if (color) cout << colors[color]; - cout << dots[dot]; + cout << (type == type_histogram ? bars[dot] : type == type_block ? blocks[dot] + : dots[dot]); if (color) cout << colors[0]; @@ -691,7 +753,7 @@ namespace graphs if (border) cout << styles[style][1]; - if (i < (height - 4) or border) + if (i < (height - ai) or border) cout << '\n'; } @@ -710,9 +772,120 @@ namespace graphs return 0; } + template + int histogram(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &aarray, /* const */ options &aoptions = histogram_defaultoptions) + { + if (!graphs::size(aarray)) + return 1; + + const color_type color = aoptions.color; + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + if (height == 0) + height = w.ws_row * 4; + + if (width == 0) + width = w.ws_col * 2; + + if (aoptions.check) + { + const size_t aheight = height / 4; + const size_t 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; + } + } + + height *= 2; + width /= 2; + + if (xmin == 0 and xmax == 0) + { + const auto &minmax = minmax_element(begin(aarray), end(aarray)); + + xmin = *minmax.first; + xmax = *minmax.second; + } + + if (xmin >= xmax) + { + cerr << "xmin must be less than xmax.\n"; + return 1; + } + + vector histogram(width, 0); + + const long double xstep = (xmax - xmin) / width; + + for (const auto &x : aarray) + { + if (x >= xmin and x < xmax) + { + const size_t index = (x - xmin) / xstep; + ++histogram[index]; + } + } + + if (ymin == 0 and ymax == 0) + { + const auto &minmax = minmax_element(histogram.begin(), histogram.end()); + + ymin = *minmax.first; + ymax = *minmax.second; + } + + if (ymin >= ymax) + { + cerr << "ymin must be less than ymax.\n"; + return 1; + } + + const long double ystep = (ymax - ymin) / height; + const long double yaxis = ymax / ystep; + + vector> aaarray(width, vector(height, 0)); + + const unsigned int acolor = color + 1; + + for (size_t x = 0; x < graphs::size(histogram); ++x) + { + const size_t ay = histogram[x]; + + for (size_t y = ay >= ymax ? 0 : yaxis - (ay / ystep); y < yaxis and y < height; ++y) + aaarray[x][y] = acolor; + } + + aoptions.type = type_histogram; + + return graph(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); + } + + template + int histogram(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const size_t rows, T *aarray, /* const */ options &aoptions = histogram_defaultoptions) + { + if (rows == 0) + return 1; + + vector aaarray(rows); + copy(aarray, aarray + rows, aaarray.begin()); + + return histogram(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); + } + // Convert one or more arrays to graph and output template - int arrays(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &arrays, const options &aoptions = defaultoptions) + int plots(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &arrays, const options &aoptions = defaultoptions) { if (!graphs::size(arrays)) return 1; @@ -754,12 +927,15 @@ namespace graphs } } + if (aoptions.type == type_block) + height /= 2; + if (xmin == 0 and xmax == 0) { const auto compare = [](const auto &a, const auto &b) { return a[0] < b[0]; }; - const auto result = accumulate(begin(arrays), end(arrays), make_pair(arrays[0][0], arrays[0][0]), [compare](const auto ¤t, const auto &array) - { const auto minmax = minmax_element(begin(array), end(array), compare); return make_pair(min(current.first, *minmax.first, compare), max(current.second, *minmax.second, compare)); }); + const auto &result = accumulate(begin(arrays), end(arrays), make_pair(arrays[0][0], arrays[0][0]), [compare](const auto ¤t, const auto &array) + { const auto &minmax = minmax_element(begin(array), end(array), compare); return make_pair(min(current.first, *minmax.first, compare), max(current.second, *minmax.second, compare)); }); xmin = result.first[0]; xmax = result.second[0]; } @@ -768,8 +944,8 @@ namespace graphs { const auto compare = [](const auto &a, const auto &b) { return a[1] < b[1]; }; - const auto result = accumulate(begin(arrays), end(arrays), make_pair(arrays[0][0], arrays[0][0]), [compare](const auto ¤t, const auto &array) - { const auto minmax = minmax_element(begin(array), end(array), compare); return make_pair(min(current.first, *minmax.first, compare), max(current.second, *minmax.second, compare)); }); + const auto &result = accumulate(begin(arrays), end(arrays), make_pair(arrays[0][0], arrays[0][0]), [compare](const auto ¤t, const auto &array) + { const auto &minmax = minmax_element(begin(array), end(array), compare); return make_pair(min(current.first, *minmax.first, compare), max(current.second, *minmax.second, compare)); }); ymin = result.first[1]; ymax = result.second[1]; } @@ -807,13 +983,22 @@ namespace graphs const size_t ax = (x / xstep) + xaxis; const size_t ay = (yaxis - (y / ystep)) - 1; - if (aarray[ax][ay]) + for (const auto &mark : marks[aoptions.mark]) { - if (aarray[ax][ay] != acolor) - aarray[ax][ay] = 1; + const size_t x = ax + mark[0]; + const size_t y = ay + mark[1]; + + if (x < width and y < height) + { + if (aarray[x][y]) + { + if (aarray[x][y] != acolor) + aarray[x][y] = 1; + } + else + aarray[x][y] = acolor; + } } - else - aarray[ax][ay] = acolor; } } } @@ -823,16 +1008,16 @@ namespace graphs // Convert single array to graph and output template - int array(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &aarray, const options &aoptions = defaultoptions) + int plot(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &aarray, const options &aoptions = defaultoptions) { const std::array aaarray = {aarray}; - return arrays(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); + return plots(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); } // Convert single array to graph and output template - int array(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const size_t rows, T **aarray, const options &aoptions = defaultoptions) + int plot(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const size_t rows, T **aarray, const options &aoptions = defaultoptions) { if (rows == 0) return 1; @@ -842,7 +1027,7 @@ namespace graphs for (size_t i = 0; i < rows; ++i) copy(aarray[i], aarray[i] + columns, aaarray[i].begin()); - return array(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); + return plot(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); } // Convert one or more functions to graph and output @@ -881,6 +1066,9 @@ namespace graphs } } + if (aoptions.type == type_block) + height /= 2; + if (xmin >= xmax) { cerr << "xmin must be less than xmax.\n"; diff --git a/python/README.md b/python/README.md index e66b6f1..6dda2ea 100644 --- a/python/README.md +++ b/python/README.md @@ -20,7 +20,7 @@ See the [tables.py](tables.py) file for full usage information. 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`. +Run with: `python3 -OO test.py`. #### Output str array as table @@ -214,9 +214,10 @@ Values: ![](../images/heavy%20table.png) 4. `style_types.double`: Double ![](../images/double%20table.png) -5. `style_types.light_dashed`: Light Dashed +5. `style_types.arc`: Light Arc +6. `style_types.light_dashed`: Light Dashed ![](../images/light%20dashed%20table.png) -6. `style_types.heavy_dashed`: Heavy Dashed +7. `style_types.heavy_dashed`: Heavy Dashed ![](../images/heavy%20dashed%20table.png) #### Check size @@ -240,10 +241,30 @@ See the [graphs.py](graphs.py) file for full usage information. 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`. +Run with: `python3 -OO 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 histogram + +```py +import graphs + +height = 160 +width = 160 + +xmin = -20 +xmax = 20 +ymin = -20 +ymax = 20 + +# Set array, can be any sequence data type + +graphs.histogram(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 resulting histogram. + #### Output single array as plot ```py @@ -259,14 +280,14 @@ ymax = 20 # Set array, can be any sequence data type, but must have exactly two columns -graphs.array(height, width, xmin, xmax, ymin, ymax, array) +graphs.plot(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) -Use `graphs.arrays()` to plot multiple arrays, which can be of different sizes. +Use `graphs.plots()` to plot multiple arrays, which can be of different sizes. #### Output single function as graph @@ -413,6 +434,27 @@ Formats 2-5 are similar to the respective `--to` options with the [numfmt](https Option: `yunits`\ Values: Same as above. +#### Type + +Option: `type`\ +Values: + +1. `type_types.braille`: Braille (default) +2. `type_types.block`: Block + +The Braille type has the highest resolution of 2×4 pixels per character, while the block type uses 2×2. This option is only used for plots and graphs. Histograms use 1×8 pixels per character. + +#### Mark type + +Option: `mark`\ +Values: + +1. `mark_types.dot`: Dot (default) +2. `mark_types.plus`: Plus +3. `mark_types.square`: Square + +The dot mark type uses a single pixel per mark, the plus uses five pixels and the square uses eight pixels. This option is only used for plots and graphs. + #### Title Option: `title`\ @@ -435,9 +477,10 @@ Values: ![](../images/heavy%20graph.png) 4. `style_types.double`: Double ![](../images/double%20graph.png) -5. `style_types.light_dashed`: Light Dashed +5. `style_types.arc`: Light Arc +6. `style_types.light_dashed`: Light Dashed ![](../images/light%20dashed%20graph.png) -6. `style_types.heavy_dashed`: Heavy Dashed +7. `style_types.heavy_dashed`: Heavy Dashed ![](../images/heavy%20dashed%20graph.png) #### Graph/Plot Color diff --git a/python/graphs.py b/python/graphs.py index a40f0b3..4bb4ac5 100644 --- a/python/graphs.py +++ b/python/graphs.py @@ -23,6 +23,7 @@ class style_types(IntEnum): light = auto() heavy = auto() double = auto() + arc = auto() light_dashed = auto() heavy_dashed = auto() @@ -33,6 +34,7 @@ styles = [ ["─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light ["━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"], # Heavy ["═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"], # Double + ["─", "│", "╭", "┬", "╮", "├", "┼", "┤", "╰", "┴", "╯"], # Light Arc ["╌", "┊", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light Dashed ["╍", "┋", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"] # Heavy Dashed # [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]] #No border @@ -83,8 +85,31 @@ dots = [ "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿"] +dotvalues = [[0x1, 0x2, 0x4, 0x40], [0x8, 0x10, 0x20, 0x80]] + +blocks = [" ", "▖", "▗", "▄", "▘", "▌", "▚", + "▙", "▝", "▞", "▐", "▟", "▀", "▛", "▜", "█"] +blockvalues = [[4, 1], [8, 2]] + +bars = [" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] + + +class type_types(IntEnum): + braille = 0 + block = auto() + histogram = auto() # Set automatically by the histogram() function + +atype_types = (type_types.braille, type_types.block) + +marks = [[[0, 0]], [[0, 1], [-1, 0], [0, 0], [1, 0], [0, -1]], + [[-1, 1], [0, 1], [1, 1], [-1, 0], [1, 0], [-1, -1], [0, -1], [1, -1]]] + + +class mark_types(IntEnum): + dot = 0 + plus = auto() + square = auto() -values = [[0x1, 0x2, 0x4, 0x40], [0x8, 0x10, 0x20, 0x80]] fractions = { "¼": Fraction(1, 4), @@ -174,9 +199,7 @@ def outputunit(number: float, scale: units_types) -> str: anumber = abs(number) anumber += 0.0005 if anumber < 10 else 0.005 if anumber < 100 else 0.05 if anumber < 1000 else 0.5 - strm = "" - - if number != 0 and anumber < 1000 and power > 0: + if number and anumber < 1000 and power > 0: strm = f"{number:.{sys.float_info.dig}n}" length = 5 + (number < 0) @@ -263,8 +286,7 @@ def outputlabel(label: float, units: units_types) -> Tuple[int, str]: return length, strm -def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, array: List[List[int]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, - axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, style: style_types = style_types.light, title: Optional[str] = None, check: bool = True) -> int: +def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, array: List[List[int]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, atype: type_types = type_types.braille, style: style_types = style_types.light, title: Optional[str] = None, check: bool = True) -> int: """Output graph.""" if not array: return 1 @@ -277,8 +299,11 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: w = shutil.get_terminal_size() - aheight = height // 4 - awidth = width // 2 + ai = 8 if atype == type_types.histogram else 2 if atype == type_types.block else 4 + aj = 1 if atype == type_types.histogram else 2 + + aheight = height // ai + awidth = width // aj if check: if aheight > w.lines: @@ -303,8 +328,8 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: ystep = (ymax - ymin) / height xaxis = 0 if xmin > 0 else width if xmax < 0 else width - xmax / xstep yaxis = height if ymin > 0 else 0 if ymax < 0 else ymax / ystep - xdivisor = 2 * 4 * (width // 160 + 2) - ydivisor = 2 * 4 * (height // 160 + 2) + xdivisor = 4 * aj * ((((2 * width) // aj) // 160) + 2) + ydivisor = 2 * ai * ((((4 * height) // ai) // 160) + 2) if title: print(textwrap.fill(title, width=awidth)) @@ -320,8 +345,8 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: i = 0 while i < height: - ayaxis = i <= yaxis < i + 4 if yaxis <= height - 4 else i < yaxis <= i + 4 - yaxislabel = i <= yaxis + 4 < i + 4 if yaxis <= height - 4 else i < yaxis - 4 <= i + 4 + ayaxis = i <= yaxis < i + ai if yaxis <= height - ai else i < yaxis <= i + ai + yaxislabel = i <= yaxis + ai < i + ai if yaxis <= height - ai else i < yaxis - ai <= i + ai ylabelstrm = "" ylabellength = 0 @@ -332,24 +357,24 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: adivisor = -ydivisor if i < yaxis else ydivisor k = yaxis + adivisor - while (k >= i if i < yaxis else k < i + 4) and i >= 4 and not output: - if i <= k < i + 4: - label = ymax - (height if k > height else k) * ystep + while (k >= i if i < yaxis else k < i + ai) and i >= ai and not output: + if i <= k < i + ai: + label = ymax - min(k, height) * ystep output = True k += adivisor if output: ylabellength, ylabelstrm = outputlabel(label, yunits) - ylabellength *= 2 + ylabellength *= aj if border: strm += styles[style][1] j = 0 while j < width: - axaxis = j < xaxis <= j + 2 if xaxis >= 2 else j <= xaxis < j + 2 - xaxislabel = j < xaxis - 2 <= j + 2 if xaxis >= 2 else j <= xaxis + 2 < j + 2 + axaxis = j < xaxis <= j + aj if xaxis >= aj else j <= xaxis < j + aj + xaxislabel = j < xaxis - aj <= j + aj if xaxis >= aj else j <= xaxis + aj < j + aj output = False @@ -361,16 +386,16 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: if i == 0: strm += styles[style][4] output = True - elif i >= height - 4: + elif i >= height - ai: strm += styles[style][10] output = True elif axistick: adivisor = -ydivisor if i < yaxis else ydivisor k = yaxis + adivisor - while (k >= i if i < yaxis else k < i + 4) and i >= 4 and not output: - if i <= k < i + 4: - strm += styles[style][7 if xaxis >= 2 else 5] + while (k >= i if i < yaxis else k < i + ai) and i >= ai and not output: + if i <= k < i + ai: + strm += styles[style][7 if xaxis >= aj else 5] output = True k += adivisor if not output: @@ -380,17 +405,17 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: if j == 0: strm += styles[style][2] output = True - elif j >= width - 2: + elif j >= width - aj: strm += styles[style][4] output = True elif axistick: adivisor = -xdivisor if j < xaxis else xdivisor k = xaxis + adivisor - while (k >= j if j < xaxis else k < j + 2) and j < width - 4 and not output: - if j <= k < j + 2: + while (k >= j if j < xaxis else k < j + aj) and j < width - ai and not output: + if j <= k < j + aj: strm += styles[style][3 if yaxis <= - height - 4 else 9] + height - ai else 9] output = True k += adivisor if not output: @@ -399,62 +424,70 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: elif yaxislabel and xaxislabel and axistick and axisunitslabel and ymin <= 0 <= ymax and xmin <= 0 <= xmax: strm += "0" output = True - elif (j >= width - 2 if xaxis <= width - 2 else j == 0) and yaxislabel and axislabel: + elif (j >= width - aj if xaxis <= width - aj else j == 0) and yaxislabel and axislabel: strm += "x" output = True elif yaxislabel and axistick and axisunitslabel: label = 0.0 adivisor = -xdivisor if j < xaxis else xdivisor if j < xaxis: - j += 2 + j += aj k = xaxis + adivisor - while (k >= j if j < xaxis else k < j + 2) and j < width - 2 and not output: - if j <= k < j + 2: - label = (width if k > width else k) * xstep + xmin + while (k >= j if j < xaxis else k < j + aj) and j < width - aj and not output: + if j <= k < j + aj: + label = min(k, width) * xstep + xmin output = True k += adivisor if adivisor < 0: - j -= 2 + j -= aj if output: output = False length, astrm = outputlabel(label, xunits) - length *= 2 - if (j >= xaxis or j + length < xaxis - 4) and j + length < width - 2: + length *= aj + if (j >= xaxis or j + length < xaxis - ai) and j + length < width - aj: strm += astrm - if length > 2: - j += length - 2 + if length > aj: + j += length - aj if adivisor < 0: output = True else: - j += 2 - elif (i == 0 if yaxis >= 4 else i >= height - 4) and xaxislabel and axislabel: + j += aj + elif (i == 0 if yaxis >= ai else i >= height - ai) and xaxislabel and axislabel: strm += "y" output = True - elif ylabellength and (xaxislabel if xaxis < 2 else j < xaxis - ylabellength and j + 2 >= xaxis - ylabellength) and (yaxis >= 4 or i < height - 4) and axistick and axisunitslabel: + elif ylabellength and (xaxislabel if xaxis < aj else j < xaxis - ylabellength and j + aj >= xaxis - ylabellength) and (yaxis >= ai or i < height - ai) and axistick and axisunitslabel: strm += ylabelstrm output = True - if ylabellength > 2: - j += ylabellength - 2 + if ylabellength > aj: + j += ylabellength - aj 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] + for k in range(min(aj, width - j)): + for l in range(min(ai, height - i)): + value = array[j + k][i + l] + if value: + if atype == type_types.histogram: + if not dot: + dot = (len(bars) - l) - 1 + elif atype == type_types.block: + dot += blockvalues[k][l] + else: + dot += dotvalues[k][l] if color: - if array[j + k][i + l] and color != array[j + k][i + l]: + if value and color != value: color = 1 else: - color = array[j + k][i + l] + color = value if color: color -= 1 @@ -462,19 +495,19 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: if color: strm += colors[color] - strm += dots[dot] + strm += bars[dot] if atype == type_types.histogram else blocks[dot] if atype == type_types.block else dots[dot] if color: strm += colors[0] - j += 2 + j += aj if border: strm += styles[style][1] - if i < height - 4 or border: + if i < height - ai or border: strm += "\n" - i += 4 + i += ai if border: strm += styles[style][8] @@ -488,8 +521,78 @@ def graph(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: return 0 -def arrays(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarrays: Sequence[Sequence[Sequence[float]]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, - xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None, check: bool = True) -> int: +def histogram(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarray: Sequence[Sequence[Sequence[float]]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None, check: bool = True) -> int: + """Convert one or more arrays to graph and output.""" + if not aarray: + return 1 + + w = shutil.get_terminal_size() + + if height == 0: + height = w.lines * 4 + + if width == 0: + width = w.columns * 2 + + if check: + aheight = height // 4 + awidth = width // 2 + + if aheight > w.lines: + print( + f"The height of the graph ({aheight}) is greater then the height of the terminal ({w.lines}).", file=sys.stderr) + return 1 + + if awidth > w.columns: + print( + f"The width of the graph ({awidth}) is greater then the width of the terminal ({w.columns}).", file=sys.stderr) + return 1 + + height *= 2 + width //= 2 + + if xmin == xmax == 0: + xmin = min(aarray) + xmax = max(aarray) + + if xmin >= xmax: + print("xmin must be less than xmax.", file=sys.stderr) + return 1 + + histogram = [0 for i in range(width)] + + xstep = (xmax - xmin) / width + + for x in aarray: + if xmin <= x < xmax: + index = int((x - xmin) / xstep) + histogram[index] += 1 + + if ymin == ymax == 0: + ymin = min(histogram) + ymax = max(histogram) + + if ymin >= ymax: + print("ymin must be less than ymax.", file=sys.stderr) + return 1 + + ystep = (ymax - ymin) / height + yaxis = ymax / ystep + + aaarray = [[0 for j in range(height)] for i in range(width)] + + acolor = color + 1 + + for x, ay in enumerate(histogram): + y = 0 if ay >= ymax else int(yaxis - (ay / ystep)) + while y < yaxis and y < height: + aaarray[x][y] = acolor + y += 1 + + return graph(height, width, xmin, xmax, ymin, ymax, aaarray, border, axis, axislabel, axistick, axisunitslabel, xunits, yunits, type_types.histogram, style, title) + + +def plots(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarrays: Sequence[Sequence[Sequence[float]]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, atype: type_types = type_types.braille, mark: mark_types = mark_types.dot, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None, check: bool = True) -> int: """Convert one or more arrays to graph and output.""" if not aarrays: return 1 @@ -520,13 +623,16 @@ def arrays(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: f"The width of the graph ({awidth}) is greater then the width of the terminal ({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 atype == type_types.block: + height //= 2 - 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 == 0: + xmin = min(x for aarray in aarrays for x, y in aarray) + xmax = max(x for aarray in aarrays for x, y in aarray) + + if ymin == ymax == 0: + ymin = min(y for aarray in aarrays for x, y in aarray) + ymax = max(y for aarray in aarrays for x, y in aarray) if xmin >= xmax: print("xmin must be less than xmax.", file=sys.stderr) @@ -546,30 +652,31 @@ def arrays(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: 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] / xstep + xaxis) - y = int(yaxis - i[1] / ystep - 1) + for x, y in aarray: + if xmin <= x < xmax and ymin <= y < ymax: + ax = int(x / xstep + xaxis) + ay = int(yaxis - y / ystep - 1) - if aaarray[x][y]: - if aaarray[x][y] != acolor: - aaarray[x][y] = 1 - else: - aaarray[x][y] = acolor + for ix, iy in marks[mark]: + x = ax + ix + y = ay + iy - return graph(height, width, xmin, xmax, ymin, ymax, aaarray, border=border, axis=axis, axislabel=axislabel, - axistick=axistick, axisunitslabel=axisunitslabel, xunits=xunits, yunits=yunits, style=style, title=title) + if x < width and y < height: + 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, axis, axislabel, axistick, axisunitslabel, xunits, yunits, atype, style, title) -def array(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarray: Sequence[Sequence[float]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, - axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None) -> int: +def plot(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, aarray: Sequence[Sequence[float]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, atype: type_types = type_types.braille, mark: mark_types = mark_types.dot, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None) -> int: """Convert single array to graph and output.""" - return arrays(height, width, xmin, xmax, ymin, ymax, [aarray], border=border, axis=axis, axislabel=axislabel, - axistick=axistick, axisunitslabel=axisunitslabel, xunits=xunits, yunits=yunits, style=style, color=color, title=title) + return plots(height, width, xmin, xmax, ymin, ymax, [aarray], border, axis, axislabel, axistick, axisunitslabel, xunits, yunits, atype, mark, style, color, title) -def functions(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, afunctions: Sequence[Callable[[float], float]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, - axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None, check: bool = True) -> int: +def functions(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, afunctions: Sequence[Callable[[float], float]], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, atype: type_types = type_types.braille, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None, check: bool = True) -> int: """Convert one or more functions to graph and output.""" if not afunctions: return 1 @@ -596,6 +703,9 @@ def functions(height: int, width: int, xmin: float, xmax: float, ymin: float, ym f"The width of the graph ({awidth}) is greater then the width of the terminal ({w.columns}).", file=sys.stderr) return 1 + if atype == type_types.block: + height //= 2 + if xmin >= xmax: print("xmin must be less than xmax.", file=sys.stderr) return 1 @@ -632,12 +742,9 @@ def functions(height: int, width: int, xmin: float, xmax: float, ymin: float, ym else: array[ax][ay] = acolor - return graph(height, width, xmin, xmax, ymin, ymax, array, border=border, axis=axis, axislabel=axislabel, - axistick=axistick, axisunitslabel=axisunitslabel, xunits=xunits, yunits=yunits, style=style, title=title) + return graph(height, width, xmin, xmax, ymin, ymax, array, border, axis, axislabel, axistick, axisunitslabel, xunits, yunits, atype, style, title) -def function(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, afunction: Callable[[float], float], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, - axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None) -> int: +def function(height: int, width: int, xmin: float, xmax: float, ymin: float, ymax: float, afunction: Callable[[float], float], border: bool = False, axis: bool = True, axislabel: bool = True, axistick: bool = True, axisunitslabel: bool = True, xunits: units_types = units_types.fracts, yunits: units_types = units_types.fracts, atype: type_types = type_types.braille, style: style_types = style_types.light, color: color_types = color_types.red, title: Optional[str] = None) -> int: """Convert single function to function array and output.""" - return functions(height, width, xmin, xmax, ymin, ymax, [afunction], border=border, axis=axis, axislabel=axislabel, - axistick=axistick, axisunitslabel=axisunitslabel, xunits=xunits, yunits=yunits, style=style, color=color, title=title) + return functions(height, width, xmin, xmax, ymin, ymax, [afunction], border, axis, axislabel, axistick, axisunitslabel, xunits, yunits, atype, style, color, title) diff --git a/python/tables.py b/python/tables.py index 20c6ca1..e3dde5e 100644 --- a/python/tables.py +++ b/python/tables.py @@ -21,6 +21,7 @@ class style_types(IntEnum): light = auto() heavy = auto() double = auto() + arc = auto() light_dashed = auto() heavy_dashed = auto() @@ -31,6 +32,7 @@ styles = [ ["─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light ["━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"], # Heavy ["═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"], # Double + ["─", "│", "╭", "┬", "╮", "├", "┼", "┤", "╰", "┴", "╯"], # Light Arc ["╌", "┊", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light Dashed ["╍", "┋", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"] # Heavy Dashed # [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]] #No border @@ -181,8 +183,7 @@ def table(array: List[List[str]], headerrow: bool = False, headercolumn: bool = return 0 -def array(aarray: Sequence[Sequence[Any]], aheaderrow: Optional[Sequence[Any]] = None, aheadercolumn: Optional[Sequence[Any]] = None, headerrow: bool = False, headercolumn: bool = False, - tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: +def array(aarray: Sequence[Sequence[Any]], aheaderrow: Optional[Sequence[Any]] = None, aheadercolumn: Optional[Sequence[Any]] = None, headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: """Convert array to char array and output as table.""" if not aarray: return 1 @@ -228,12 +229,10 @@ def array(aarray: Sequence[Sequence[Any]], aheaderrow: Optional[Sequence[Any]] = j = 1 if aheadercolumn else 0 aaarray[i][j:] = map(str, aarray[ii][:columns - j]) - return table(aaarray, headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, - cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) + return table(aaarray, headerrow, headercolumn, tableborder, cellborder, padding, alignment, title, style) -def functions(xmin: float, xmax: float, xstep: float, afunctions: Sequence[Callable[[float], float]], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, - cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: +def functions(xmin: float, xmax: float, xstep: float, afunctions: Sequence[Callable[[float], float]], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: """Convert one or more functions to array and output as table.""" if not afunctions: return 1 @@ -263,16 +262,13 @@ def functions(xmin: float, xmax: float, xstep: float, afunctions: Sequence[Calla aarray = [[0 for j in range(columns)] for i in range(rows)] for i in range(rows): - aarray[i][0] = i * xstep + xmin + temp = aarray[i][0] = i * xstep + xmin - aarray[i][1:] = [function(aarray[i][0]) for function in afunctions] + aarray[i][1:] = [function(temp) for function in afunctions] - return array(aarray, aheaderrow, None, headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, - cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) + return array(aarray, aheaderrow, None, headerrow, headercolumn, tableborder, cellborder, padding, alignment, title, style) -def function(xmin: float, xmax: float, xstep: float, afunction: Callable[[float], float], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, - cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: +def function(xmin: float, xmax: float, xstep: float, afunction: Callable[[float], float], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: """Convert single function to array and output as table.""" - return functions(xmin, xmax, xstep, [afunction], headerrow=headerrow, headercolumn=headercolumn, - tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) + return functions(xmin, xmax, xstep, [afunction], headerrow, headercolumn, tableborder, cellborder, padding, alignment, title, style) diff --git a/python/test.py b/python/test.py index 57846cf..9ab8865 100644 --- a/python/test.py +++ b/python/test.py @@ -2,6 +2,8 @@ # Teal Dulcet, CS546 +# Run: python3 -OO test.py + import math import random import sys @@ -112,10 +114,18 @@ xmax = 20 ymin = -20 ymax = 20 +print("\nOutput array as histogram\n") +array = [random.gauss(0, 1) for i in range(100)] +for style in graphs.style_types: + graphs.histogram(height, width, xmin, xmax, ymin, ymax, array, style=style) + print("\nOutput single array as plot\n") array = [range(i, i + 2) for i in range(10)] -for style in graphs.style_types: - graphs.array(height, width, xmin, xmax, ymin, ymax, array, style=style) +for atype in graphs.atype_types: + for mark in graphs.mark_types: + for style in graphs.style_types: + graphs.plot(height, width, xmin, xmax, ymin, ymax, + array, atype=atype, mark=mark, style=style) print("\nOutput single function as graph\n") for style in graphs.style_types: diff --git a/tables.cpp b/tables.cpp index 974addc..816ecf7 100644 --- a/tables.cpp +++ b/tables.cpp @@ -1,6 +1,6 @@ // Teal Dulcet, CS546 -// Compile: g++ -Wall -g -O3 -std=c++14 tables.cpp -o tables +// Compile: g++ -std=c++14 -Wall -g -O3 tables.cpp -o tables // Run: ./tables @@ -87,11 +87,11 @@ int main() { long long **array; array = new long long *[rows]; - for (unsigned int i = 0; i < rows; ++i) + for (unsigned 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) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = rand(); tables::options aoptions; @@ -105,7 +105,7 @@ int main() if (array) { - for (unsigned int i = 0; i < rows; ++i) + for (unsigned i = 0; i < rows; ++i) delete[] array[i]; delete[] array; @@ -114,8 +114,8 @@ int main() { array, rows> aarray; - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) aarray[i][j] = rand(); tables::options aoptions; @@ -130,8 +130,8 @@ int main() { vector> array(rows, vector(columns)); - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = rand(); tables::options aoptions; @@ -146,11 +146,11 @@ int main() { long double **array; array = new long double *[rows]; - for (unsigned int i = 0; i < rows; ++i) + for (unsigned 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) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = static_cast(rand()) / static_cast(RAND_MAX); tables::options aoptions; @@ -164,7 +164,7 @@ int main() if (array) { - for (unsigned int i = 0; i < rows; ++i) + for (unsigned i = 0; i < rows; ++i) delete[] array[i]; delete[] array; @@ -173,8 +173,8 @@ int main() { array, rows> aarray; - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) aarray[i][j] = static_cast(rand()) / static_cast(RAND_MAX); tables::options aoptions; @@ -189,8 +189,8 @@ int main() { vector> array(rows, vector(columns)); - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = static_cast(rand()) / static_cast(RAND_MAX); tables::options aoptions; @@ -402,11 +402,11 @@ int main() { bool **array; array = new bool *[rows]; - for (unsigned int i = 0; i < rows; ++i) + for (unsigned 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) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = rand() % 2; tables::options aoptions; @@ -422,7 +422,7 @@ int main() if (array) { - for (unsigned int i = 0; i < rows; ++i) + for (unsigned i = 0; i < rows; ++i) delete[] array[i]; delete[] array; @@ -433,11 +433,11 @@ int main() { int **array; array = new int *[rows]; - for (unsigned int i = 0; i < rows; ++i) + for (unsigned 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) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = rand(); dimensions = columns; @@ -457,7 +457,7 @@ int main() if (array) { - for (unsigned int i = 0; i < rows; ++i) + for (unsigned i = 0; i < rows; ++i) delete[] array[i]; delete[] array; @@ -466,8 +466,8 @@ int main() { array, rows> aarray; - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) aarray[i][j] = rand(); dimensions = columns; @@ -487,8 +487,8 @@ int main() { vector> array(rows, vector(columns)); - for (unsigned int i = 0; i < rows; ++i) - for (unsigned int j = 0; j < columns; ++j) + for (unsigned i = 0; i < rows; ++i) + for (unsigned j = 0; j < columns; ++j) array[i][j] = rand(); dimensions = columns; diff --git a/tables.hpp b/tables.hpp index 50e4f3f..3e2c833 100644 --- a/tables.hpp +++ b/tables.hpp @@ -25,11 +25,12 @@ namespace tables style_light, style_heavy, style_double, + style_arc, style_light_dashed, style_heavy_dashed }; - enum style_type const style_types[] = {style_ASCII, style_basic, style_light, style_heavy, style_double, style_light_dashed, style_heavy_dashed}; + enum style_type const style_types[] = {style_ASCII, style_basic, style_light, style_heavy, style_double, style_arc, style_light_dashed, style_heavy_dashed}; const char *const styles[][11] = { {"-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, // ASCII @@ -37,6 +38,7 @@ namespace tables {"─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, // Light {"━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"}, // Heavy {"═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"}, // Double + {"─", "│", "╭", "┬", "╮", "├", "┼", "┤", "╰", "┴", "╯"}, // Light Arc {"╌", "┊", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, // Light Dashed {"╍", "┋", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"} // Heavy Dashed // {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "} // No border