From 30beea8b75ab1dd7fc5751df4bb15f6c014c2fe9 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:58:03 +0100 Subject: [PATCH 01/20] intermediate texture preview --- graphs.hpp | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/graphs.hpp b/graphs.hpp index d32855d..2dcbefb 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -169,6 +169,30 @@ namespace graphs bool check = true; }; + // Structure for 24-bit true colors + struct true_color + { + uint8_t red; + uint8_t green; + uint8_t blue; + }; + // Using the existing color_type enum for the 4-bit colors (not using std::variant due to its larger size) + using color = union { + color_type col_4; + uint8_t col_8; + struct { // 24-bit true color + uint8_t r; + uint8_t g; + uint8_t b; + }; + }; + // Intermediate fragment representation potentially holding multiple pixels (e.g. 2x4 as braille) + // most optimal representation possible with only 4 bytes in size, applicable for all types of characters + struct fragment { + color color; + uint8_t data_point_bitfield; // stores up to 8 data points + }; + // Number of columns needed to represent the string // Adapted from: https://stackoverflow.com/a/31124065 inline int strcol(const string &str) @@ -754,6 +778,104 @@ namespace graphs return 0; } + // EXPERIMENTAL BEG + template // TODO: remove templating? + void histogram_experimental(size_t height, size_t width, double x_min, double x_max, double y_min, double y_max, const T &data, const options &aoptions = {}) { + cout << "Experimental histogram\n"; + + // precalc graph span + const double x_size = x_max - x_min; + const double y_size = y_max - y_min; + // need width+height of chosen character set (using bar characters as example) + const size_t char_width = 1; // bar supports 1 state per character width + const size_t char_height = 8; // bar supports 8 states per character height + // calc how many data points we can represent with current character set and width/height + const size_t x_points = width * char_width; + const size_t y_points = height * char_height; + // for histograms, every sample needs at least one data point to occupy in x + const size_t x_bar_size = 1; // temporarily fixed to 1 + // given the x_bar_size (which should be exposed as an option), see how many points will be placed between bars + const double x_bar_spacer = (double)x_points / ((double)x_bar_size * x_size) - 1; + + // histogram specific: each bar needs at least one data point + if ((double)x_points / x_size < 1) { + cerr << "width is too small to fit all histogram bars\n"; + return; + } + + cout << "width : " << width << '\n'; + cout << "height: " << height << '\n'; + cout << "x_size: " << x_size << '\n'; + cout << "y_size: " << y_size << '\n'; + cout << '\n'; + cout << "char_width: " << char_width << '\n'; + cout << "char_height: " << char_height << '\n'; + cout << "x_points: " << x_points << '\n'; + cout << "y_points: " << y_points << '\n'; + cout << "x_bar_size: " << x_bar_size << '\n'; + cout << "x_bar_spacer: " << x_bar_spacer << '\n'; + + // simple histogram as vector of sample counts + vector histogram(x_size, 0); + for (const auto &x: data) { + // check if value is between limits + if (x >= x_min && x < x_max) { + // calculate index on x-axis + const size_t index = x - x_min; + // increment height of the bar + ++histogram[index]; + } + } + + // create 2D array of temporary fragments for the graph + vector tex(width * height); + // insert draw histogram data into texture + for (size_t x = 0; x < histogram.size(); x++) { + // calc bar position on x-axis + const size_t x_pos = x * (x_bar_size + x_bar_spacer); + + // read bar size from histogram + const size_t y_histo = histogram[x]; + // scale to 0-1 range via max bar size + const double y_scaled = (double)y_histo / y_size; + // scale back up via point size + const size_t y_target = y_scaled * (double)y_points; + // calc fragment position and remainder for cap + const size_t y_tex = y_target / char_height; + const size_t y_cap = y_target % char_height; + + // draw bar body + for (size_t y = 0; y < y_tex; y++) { + // in texture, y=0 is at the top, so we need to invert the y-axis + const size_t index = x_pos + (height - 1 - y) * width; + tex[index].color.col_4 = aoptions.color; + tex[index].data_point_bitfield = 8; // use full height of character + } + + // draw bar cap + if (y_cap > 0) { + // in texture, y=0 is at the top, so we need to invert the y-axis + const size_t index = x_pos + (height - 1 - y_tex) * width; + tex[index].color.col_4 = aoptions.color; + tex[index].data_point_bitfield = y_cap; // use remainder to fill up character + } + } + + // draw graph for experimental preview purposes only + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + const size_t index = x + y * width; + const auto& frag = tex[index]; + // draw fragment + cout << colors[frag.color.col_4]; + cout << bars[frag.data_point_bitfield]; + cout << colors[0]; + } + cout << '\n'; + } + } + // EXPERIMENTAL END + 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 = {}) { From 366512d614a0c678d350d083c7293d5548af738c Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:15:19 +0100 Subject: [PATCH 02/20] prevent color name duplicate --- graphs.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 2dcbefb..7372e68 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -189,7 +189,7 @@ namespace graphs // Intermediate fragment representation potentially holding multiple pixels (e.g. 2x4 as braille) // most optimal representation possible with only 4 bytes in size, applicable for all types of characters struct fragment { - color color; + color col; uint8_t data_point_bitfield; // stores up to 8 data points }; @@ -848,7 +848,7 @@ namespace graphs for (size_t y = 0; y < y_tex; y++) { // in texture, y=0 is at the top, so we need to invert the y-axis const size_t index = x_pos + (height - 1 - y) * width; - tex[index].color.col_4 = aoptions.color; + tex[index].col.col_4 = aoptions.color; tex[index].data_point_bitfield = 8; // use full height of character } @@ -856,7 +856,7 @@ namespace graphs if (y_cap > 0) { // in texture, y=0 is at the top, so we need to invert the y-axis const size_t index = x_pos + (height - 1 - y_tex) * width; - tex[index].color.col_4 = aoptions.color; + tex[index].col.col_4 = aoptions.color; tex[index].data_point_bitfield = y_cap; // use remainder to fill up character } } @@ -867,7 +867,7 @@ namespace graphs const size_t index = x + y * width; const auto& frag = tex[index]; // draw fragment - cout << colors[frag.color.col_4]; + cout << colors[frag.col.col_4]; cout << bars[frag.data_point_bitfield]; cout << colors[0]; } From 316d2105d34ded8303dd7917da4b42a74b56670e Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:11:45 +0100 Subject: [PATCH 03/20] consolidate options --- graphs.hpp | 124 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 7372e68..ff3611e 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -54,7 +54,7 @@ namespace graphs // {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "} // No border }; - enum color_type + enum color_type: uint8_t { color_default, color_black, @@ -169,29 +169,7 @@ namespace graphs bool check = true; }; - // Structure for 24-bit true colors - struct true_color - { - uint8_t red; - uint8_t green; - uint8_t blue; - }; - // Using the existing color_type enum for the 4-bit colors (not using std::variant due to its larger size) - using color = union { - color_type col_4; - uint8_t col_8; - struct { // 24-bit true color - uint8_t r; - uint8_t g; - uint8_t b; - }; - }; - // Intermediate fragment representation potentially holding multiple pixels (e.g. 2x4 as braille) - // most optimal representation possible with only 4 bytes in size, applicable for all types of characters - struct fragment { - color col; - uint8_t data_point_bitfield; // stores up to 8 data points - }; + // Number of columns needed to represent the string // Adapted from: https://stackoverflow.com/a/31124065 @@ -779,10 +757,70 @@ namespace graphs } // EXPERIMENTAL BEG - template // TODO: remove templating? - void histogram_experimental(size_t height, size_t width, double x_min, double x_max, double y_min, double y_max, const T &data, const options &aoptions = {}) { + + // Using the existing color_type enum for the 4-bit colors (not using std::variant due to its larger size) + union Color { + color_type col_4; // 4-bit color + uint8_t col_8; // 8-bit color + struct { + uint8_t r; + uint8_t g; + uint8_t b; + } col_24; // 24-bit true color + }; + enum class ColorBits: uint8_t { e4, e8, e24 }; + + // Intermediate fragment representation potentially holding multiple pixels (e.g. 2x4 as braille) + // most optimal representation possible with only 4 bytes in size, applicable for all types of characters + struct Fragment { + Color color; + uint8_t data; // stores up to 8 data points or up to 255 values + }; + struct Options { + size_t width = 0; // Width in terminal characters. Set to 0 for automatic size based on terminal. + size_t height = 0; // Height in terminal characters. Set to 0 for automatic size based on terminal. + + struct Axis { + double min = 0; // Start of axis. Set to 0 for automatic size based on data. + double max = 0; // End of axis. Set to 0 for automatic size based on data. + bool enabled = true; + bool label = true; + bool ticks = true; + bool tick_labels = true; + units_type tick_label_type = units_fracts; + }; + Axis x = {}; + Axis y = {}; + + type_type character_set = type_braille; + style_type style = style_light; + ColorBits color_type = ColorBits::e4; + bool validate = true; // validate sizes for graph draw + bool border = false; + + struct Histogram { + size_t bar_width = 1; // size of each bar in x-axis (in data points, e.g. braille has width of 2 per terminal character) + }; + // Union for different graph type options + union { + Histogram histogram; + }; + + std::ostream &ostr = cout; + const char *title = nullptr; + }; + template + void histogram_experimental(const Options& options, const T &data) { cout << "Experimental histogram\n"; + // TODO: automatically set sizes if stuff is 0 + const double x_max = options.x.max; + const double x_min = options.x.min; + const double y_max = options.y.max; + const double y_min = options.y.min; + const size_t width = options.width; + const size_t height = options.height; + // precalc graph span const double x_size = x_max - x_min; const double y_size = y_max - y_min; @@ -793,7 +831,7 @@ namespace graphs const size_t x_points = width * char_width; const size_t y_points = height * char_height; // for histograms, every sample needs at least one data point to occupy in x - const size_t x_bar_size = 1; // temporarily fixed to 1 + const size_t x_bar_size = options.histogram.bar_width; // given the x_bar_size (which should be exposed as an option), see how many points will be placed between bars const double x_bar_spacer = (double)x_points / ((double)x_bar_size * x_size) - 1; @@ -803,18 +841,6 @@ namespace graphs return; } - cout << "width : " << width << '\n'; - cout << "height: " << height << '\n'; - cout << "x_size: " << x_size << '\n'; - cout << "y_size: " << y_size << '\n'; - cout << '\n'; - cout << "char_width: " << char_width << '\n'; - cout << "char_height: " << char_height << '\n'; - cout << "x_points: " << x_points << '\n'; - cout << "y_points: " << y_points << '\n'; - cout << "x_bar_size: " << x_bar_size << '\n'; - cout << "x_bar_spacer: " << x_bar_spacer << '\n'; - // simple histogram as vector of sample counts vector histogram(x_size, 0); for (const auto &x: data) { @@ -827,8 +853,8 @@ namespace graphs } } - // create 2D array of temporary fragments for the graph - vector tex(width * height); + // create 2D array of temporary Fragments for the graph + vector tex(width * height); // insert draw histogram data into texture for (size_t x = 0; x < histogram.size(); x++) { // calc bar position on x-axis @@ -840,7 +866,7 @@ namespace graphs const double y_scaled = (double)y_histo / y_size; // scale back up via point size const size_t y_target = y_scaled * (double)y_points; - // calc fragment position and remainder for cap + // calc Fragment position and remainder for cap const size_t y_tex = y_target / char_height; const size_t y_cap = y_target % char_height; @@ -848,16 +874,16 @@ namespace graphs for (size_t y = 0; y < y_tex; y++) { // in texture, y=0 is at the top, so we need to invert the y-axis const size_t index = x_pos + (height - 1 - y) * width; - tex[index].col.col_4 = aoptions.color; - tex[index].data_point_bitfield = 8; // use full height of character + tex[index].color.col_4 = color_red; + tex[index].data = 8; // use full height of character } // draw bar cap if (y_cap > 0) { // in texture, y=0 is at the top, so we need to invert the y-axis const size_t index = x_pos + (height - 1 - y_tex) * width; - tex[index].col.col_4 = aoptions.color; - tex[index].data_point_bitfield = y_cap; // use remainder to fill up character + tex[index].color.col_4 = color_red; + tex[index].data = y_cap; // use remainder to fill up character } } @@ -866,9 +892,9 @@ namespace graphs for (size_t x = 0; x < width; x++) { const size_t index = x + y * width; const auto& frag = tex[index]; - // draw fragment - cout << colors[frag.col.col_4]; - cout << bars[frag.data_point_bitfield]; + // draw Fragment + cout << colors[frag.color.col_4]; + cout << bars[frag.data]; cout << colors[0]; } cout << '\n'; From d57f3c6aad07efbc86dd8e331e0d582af86b4427 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:57:58 +0100 Subject: [PATCH 04/20] signatures for multi-graph draws --- graphs.hpp | 48 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index ff3611e..8763ae3 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -1,6 +1,8 @@ // Teal Dulcet, CS546 #pragma once +#include #include +#include #include #include #include @@ -776,6 +778,8 @@ namespace graphs Color color; uint8_t data; // stores up to 8 data points or up to 255 values }; + // store fragments on temporary buffer to pass between draws + using Texture = std::unique_ptr>; struct Options { size_t width = 0; // Width in terminal characters. Set to 0 for automatic size based on terminal. size_t height = 0; // Height in terminal characters. Set to 0 for automatic size based on terminal. @@ -798,19 +802,21 @@ namespace graphs bool validate = true; // validate sizes for graph draw bool border = false; + // some idea?: + std::vector colors; + std::vector x_offset; // x-axis offset for each data point of graph at that index + + // graph-specific options struct Histogram { size_t bar_width = 1; // size of each bar in x-axis (in data points, e.g. braille has width of 2 per terminal character) - }; - // Union for different graph type options - union { - Histogram histogram; - }; + } histogram; - std::ostream &ostr = cout; - const char *title = nullptr; + std::ostream &ostr = std::cout; + const char* title = nullptr; }; template - void histogram_experimental(const Options& options, const T &data) { + auto histogram_experimental(const Options& options, const std::vector& data, const Color& color = {color_red}, Texture&& texture = std::make_unique>()) -> Texture&& { + static_assert(std::numeric_limits::is_integer, "Only integer types are supported for histogram data"); cout << "Experimental histogram\n"; // TODO: automatically set sizes if stuff is 0 @@ -833,12 +839,12 @@ namespace graphs // for histograms, every sample needs at least one data point to occupy in x const size_t x_bar_size = options.histogram.bar_width; // given the x_bar_size (which should be exposed as an option), see how many points will be placed between bars - const double x_bar_spacer = (double)x_points / ((double)x_bar_size * x_size) - 1; + const size_t x_bar_spacer = (double)x_points / ((double)x_bar_size * x_size) - 1; // histogram specific: each bar needs at least one data point - if ((double)x_points / x_size < 1) { + if ((double)x_points / (double)x_bar_size < x_size) { cerr << "width is too small to fit all histogram bars\n"; - return; + return std::move(texture); } // simple histogram as vector of sample counts @@ -854,7 +860,9 @@ namespace graphs } // create 2D array of temporary Fragments for the graph - vector tex(width * height); + if (texture->size() == 0) texture->resize(width * height); + assert(texture->size() == width * height); + vector& tex = *texture; // insert draw histogram data into texture for (size_t x = 0; x < histogram.size(); x++) { // calc bar position on x-axis @@ -874,7 +882,7 @@ namespace graphs for (size_t y = 0; y < y_tex; y++) { // in texture, y=0 is at the top, so we need to invert the y-axis const size_t index = x_pos + (height - 1 - y) * width; - tex[index].color.col_4 = color_red; + tex[index].color = color; tex[index].data = 8; // use full height of character } @@ -882,7 +890,7 @@ namespace graphs if (y_cap > 0) { // in texture, y=0 is at the top, so we need to invert the y-axis const size_t index = x_pos + (height - 1 - y_tex) * width; - tex[index].color.col_4 = color_red; + tex[index].color = color; tex[index].data = y_cap; // use remainder to fill up character } } @@ -899,6 +907,18 @@ namespace graphs } cout << '\n'; } + return std::move(texture); + } + // print histogram using multiple data sets + template + void histogram_experimental(const Options& options, const std::vector>& datasets, const std::vector& colors = {}) { + Texture texture = std::make_unique>(); + // recursively call for extra data sets + for (size_t i = 0; i < datasets.size(); i++) { + // pick default color if not enough colors are provided + Color color = i < colors.size() ? colors[i] : Color{color_red}; + texture = histogram_experimental(options, datasets[i], color, std::move(texture)); + } } // EXPERIMENTAL END From 1b67cc676715bdbba2133a3e486b43a093bdda62 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:07:10 +0100 Subject: [PATCH 05/20] changed signature of single+multi histo draw --- graphs.hpp | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 8763ae3..dfc2e2e 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -171,8 +171,6 @@ namespace graphs bool check = true; }; - - // Number of columns needed to represent the string // Adapted from: https://stackoverflow.com/a/31124065 inline int strcol(const string &str) @@ -787,24 +785,21 @@ namespace graphs struct Axis { double min = 0; // Start of axis. Set to 0 for automatic size based on data. double max = 0; // End of axis. Set to 0 for automatic size based on data. - bool enabled = true; - bool label = true; + bool drawn = true; + bool label = true; // TODO: allow setting custom label string? bool ticks = true; bool tick_labels = true; units_type tick_label_type = units_fracts; }; Axis x = {}; Axis y = {}; - + type_type character_set = type_braille; style_type style = style_light; - ColorBits color_type = ColorBits::e4; + ColorBits color_type = ColorBits::e4; // bit depth of color representation bool validate = true; // validate sizes for graph draw - bool border = false; - - // some idea?: - std::vector colors; - std::vector x_offset; // x-axis offset for each data point of graph at that index + bool border = false; // draw border around the graph + /* 5 bytes padding */ // graph-specific options struct Histogram { @@ -814,8 +809,9 @@ namespace graphs std::ostream &ostr = std::cout; const char* title = nullptr; }; + // print histogram using single data set, drawn on top of existing texture template - auto histogram_experimental(const Options& options, const std::vector& data, const Color& color = {color_red}, Texture&& texture = std::make_unique>()) -> Texture&& { + auto histogram_experimental(const std::vector& data, Texture&& texture, const Options& options = {}, const Color& color = {color_red}) -> Texture&& { static_assert(std::numeric_limits::is_integer, "Only integer types are supported for histogram data"); cout << "Experimental histogram\n"; @@ -909,16 +905,26 @@ namespace graphs } return std::move(texture); } - // print histogram using multiple data sets + // print histogram using single data set template - void histogram_experimental(const Options& options, const std::vector>& datasets, const std::vector& colors = {}) { - Texture texture = std::make_unique>(); - // recursively call for extra data sets + auto histogram_experimental(const std::vector& data, const Options& options = {}, const Color& color = {color_red}) -> Texture&& { + return histogram_experimental(data, std::make_unique(), options, color); + } + // print histogram using multiple data sets, drawn on top of existing texture + template + auto histogram_experimental(const std::vector>& datasets, Texture&& texture, const Options& options = {}, const std::vector& colors = {}) -> Texture&& { + // recursively call for each data set for (size_t i = 0; i < datasets.size(); i++) { // pick default color if not enough colors are provided Color color = i < colors.size() ? colors[i] : Color{color_red}; - texture = histogram_experimental(options, datasets[i], color, std::move(texture)); + texture = histogram_experimental(datasets[i], std::move(texture), options, colors[i]); } + return std::move(texture); + } + // print histogram using multiple data sets + template + auto histogram_experimental(const std::vector>& datasets, const Options& options = {}, const std::vector& colors = {}) -> Texture&& { + return histogram_experimental(datasets, std::make_unique(), options, colors); } // EXPERIMENTAL END From 68d18a05cfe6a53cc69271b1c771e67b3e1cc56f Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:12:41 +0100 Subject: [PATCH 06/20] adjusted proposed signatures --- graphs.hpp | 59 ++++++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index dfc2e2e..8a9be75 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -778,41 +778,36 @@ namespace graphs }; // store fragments on temporary buffer to pass between draws using Texture = std::unique_ptr>; + struct Axis { + long double min = 0; + long double max = 0; + bool labels = true; + bool ticks = true; + bool units_label = true; + units_type units = units_fracts; + }; struct Options { size_t width = 0; // Width in terminal characters. Set to 0 for automatic size based on terminal. size_t height = 0; // Height in terminal characters. Set to 0 for automatic size based on terminal. - struct Axis { - double min = 0; // Start of axis. Set to 0 for automatic size based on data. - double max = 0; // End of axis. Set to 0 for automatic size based on data. - bool drawn = true; - bool label = true; // TODO: allow setting custom label string? - bool ticks = true; - bool tick_labels = true; - units_type tick_label_type = units_fracts; - }; Axis x = {}; Axis y = {}; type_type character_set = type_braille; + plot_type plot = plot_scatter; style_type style = style_light; - ColorBits color_type = ColorBits::e4; // bit depth of color representation - bool validate = true; // validate sizes for graph draw - bool border = false; // draw border around the graph - /* 5 bytes padding */ + graph_type graph = graph_dot; - // graph-specific options - struct Histogram { - size_t bar_width = 1; // size of each bar in x-axis (in data points, e.g. braille has width of 2 per terminal character) - } histogram; - + std::string title; std::ostream &ostr = std::cout; - const char* title = nullptr; + + ColorBits color_type = ColorBits::e4; // bit depth of color representation + bool check = true; // validate sizes for graph draw + bool border = false; // draw border around the graph }; - // print histogram using single data set, drawn on top of existing texture + // print histogram using single data set, optionally drawn on top of existing texture template - auto histogram_experimental(const std::vector& data, Texture&& texture, const Options& options = {}, const Color& color = {color_red}) -> Texture&& { - static_assert(std::numeric_limits::is_integer, "Only integer types are supported for histogram data"); + auto histogram_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}, Texture &&texture = std::make_unique()) -> Texture&& { cout << "Experimental histogram\n"; // TODO: automatically set sizes if stuff is 0 @@ -833,7 +828,7 @@ namespace graphs const size_t x_points = width * char_width; const size_t y_points = height * char_height; // for histograms, every sample needs at least one data point to occupy in x - const size_t x_bar_size = options.histogram.bar_width; + const size_t x_bar_size = 1; // given the x_bar_size (which should be exposed as an option), see how many points will be placed between bars const size_t x_bar_spacer = (double)x_points / ((double)x_bar_size * x_size) - 1; @@ -905,27 +900,17 @@ namespace graphs } return std::move(texture); } - // print histogram using single data set - template - auto histogram_experimental(const std::vector& data, const Options& options = {}, const Color& color = {color_red}) -> Texture&& { - return histogram_experimental(data, std::make_unique(), options, color); - } // print histogram using multiple data sets, drawn on top of existing texture template - auto histogram_experimental(const std::vector>& datasets, Texture&& texture, const Options& options = {}, const std::vector& colors = {}) -> Texture&& { + auto histogram_experimental(const T &data, const size_t rows, const Options &options = {}, const std::vector &colors = {}, Texture &&texture = std::make_unique()) -> Texture&& { // recursively call for each data set - for (size_t i = 0; i < datasets.size(); i++) { + for (size_t row = 0; row < rows; row++) { // pick default color if not enough colors are provided - Color color = i < colors.size() ? colors[i] : Color{color_red}; - texture = histogram_experimental(datasets[i], std::move(texture), options, colors[i]); + Color color = row < colors.size() ? colors[row] : Color{color_red}; + texture = histogram_experimental(data[row], options, color, std::move(texture)); } return std::move(texture); } - // print histogram using multiple data sets - template - auto histogram_experimental(const std::vector>& datasets, const Options& options = {}, const std::vector& colors = {}) -> Texture&& { - return histogram_experimental(datasets, std::make_unique(), options, colors); - } // EXPERIMENTAL END template From 950e3391a8c1a86efc1575287ff1c0ba31f57663 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:30:16 +0100 Subject: [PATCH 07/20] dedicated drawing func --- graphs.hpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 8a9be75..172072a 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -763,9 +763,9 @@ namespace graphs color_type col_4; // 4-bit color uint8_t col_8; // 8-bit color struct { - uint8_t r; - uint8_t g; - uint8_t b; + uint8_t red; + uint8_t green; + uint8_t blue; } col_24; // 24-bit true color }; enum class ColorBits: uint8_t { e4, e8, e24 }; @@ -804,7 +804,16 @@ namespace graphs ColorBits color_type = ColorBits::e4; // bit depth of color representation bool check = true; // validate sizes for graph draw bool border = false; // draw border around the graph + bool draw_immediately = true; // draw graph immediately after creation. otherwise call draw/graph with the returned texture }; + // use a graph texture to draw a graph into the terminal + void graph(Texture& texture, Options &options) { + // TODO + } + // use a graph texture to draw a graph into the terminal + void draw(Texture& texture, Options &options) { + graph(texture, options); + } // print histogram using single data set, optionally drawn on top of existing texture template auto histogram_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}, Texture &&texture = std::make_unique()) -> Texture&& { From 1d6e7ab7164acda93e5fff1655147920666471a2 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:50:54 +0100 Subject: [PATCH 08/20] optional draw --- graphs.hpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 172072a..ba127e7 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -807,11 +807,24 @@ namespace graphs bool draw_immediately = true; // draw graph immediately after creation. otherwise call draw/graph with the returned texture }; // use a graph texture to draw a graph into the terminal - void graph(Texture& texture, Options &options) { - // TODO + inline void graph(Texture& texture, const Options &options) { + vector& tex = *texture; + + // draw graph for experimental preview purposes only + for (size_t y = 0; y < options.height; y++) { + for (size_t x = 0; x < options.width; x++) { + const size_t index = x + y * options.width; + const auto& frag = tex[index]; + // draw Fragment + cout << colors[frag.color.col_4]; + cout << bars[frag.data]; + cout << colors[0]; + } + cout << '\n'; + } } // use a graph texture to draw a graph into the terminal - void draw(Texture& texture, Options &options) { + inline void draw(Texture& texture, const Options &options) { graph(texture, options); } // print histogram using single data set, optionally drawn on top of existing texture @@ -895,18 +908,7 @@ namespace graphs } } - // draw graph for experimental preview purposes only - for (size_t y = 0; y < height; y++) { - for (size_t x = 0; x < width; x++) { - const size_t index = x + y * width; - const auto& frag = tex[index]; - // draw Fragment - cout << colors[frag.color.col_4]; - cout << bars[frag.data]; - cout << colors[0]; - } - cout << '\n'; - } + if (options.draw_immediately) draw(texture, options); return std::move(texture); } // print histogram using multiple data sets, drawn on top of existing texture From fc29acf84410b5d1635307b607d985ef3f33f052 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:44:19 +0100 Subject: [PATCH 09/20] plot draft --- graphs.hpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/graphs.hpp b/graphs.hpp index ba127e7..63501f5 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -922,6 +922,46 @@ namespace graphs } return std::move(texture); } + + // plot from single data set, optionally drawn on top of existing texture + template + auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}, Texture &&texture = std::make_unique()) -> Texture&& { + cout << "Experimental plot\n"; + + // precalc spans + const long double x_span = options.x.max - options.x.min; + const long double y_span = options.y.max - options.y.min; + const long double x_span_recip = 1.0 / x_span; + const long double y_span_recip = 1.0 / y_span; + + // create 2D array of temporary Fragments for the graph + if (texture->size() == 0) texture->resize(options.width * options.height); + assert(texture->size() == options.width * options.height); + vector& tex = *texture; + + // insert draw plot data into texture + for (const auto [x, y]: data) { + // check if value is between limits + if (x >= options.x.min && x < options.x.max && y >= options.y.min && y < options.y.max) { + // calculate terminal character position + const long double x_term = ((long double)x - options.x.min) * x_span_recip * (long double)options.width; + const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; + // calculate sub-fragment position (2x4 for braille) + const size_t x_sub = (x_term - std::floor(x_term)) * 2; + const size_t y_sub = (y_term - std::floor(y_term)) * 4; + + // draw Fragment + const size_t index = (size_t)x_term + (size_t)y_term * options.width; + // TODO: mix colors here when color is 24-bit (can we mix 8-bit color?) + tex[index].color = color; + // TODO: which bit should correspond to which braille dot? + // the dot position within this fragment is (x_sub, y_sub) + // bottom left is (0, 0), top right is (1, 3) + tex[index].data |= 1 << (x_sub + y_sub * 2); + } + } + return std::move(texture); + } // EXPERIMENTAL END template From a1057fe32c14fef4d02e089cb598e29ff50ec033 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:50:54 +0100 Subject: [PATCH 10/20] intermediate representation for improved syntax and options passing --- graphs.hpp | 172 ++++++++++++++++++----------------------------------- 1 file changed, 58 insertions(+), 114 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 63501f5..8458203 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -777,7 +777,7 @@ namespace graphs uint8_t data; // stores up to 8 data points or up to 255 values }; // store fragments on temporary buffer to pass between draws - using Texture = std::unique_ptr>; + using Texture = std::vector; struct Axis { long double min = 0; long double max = 0; @@ -806,126 +806,37 @@ namespace graphs bool border = false; // draw border around the graph bool draw_immediately = true; // draw graph immediately after creation. otherwise call draw/graph with the returned texture }; + inline void graph(Texture& texture, const Options &options); + // intermediate representation of a graph texture for ease of passing around + struct Intermediate { + // use a graph texture to draw into the terminal + inline void draw() { + graph(texture, options); + } + + Texture texture; + const Options options; + }; + // use a graph texture to draw a graph into the terminal inline void graph(Texture& texture, const Options &options) { - vector& tex = *texture; - // draw graph for experimental preview purposes only for (size_t y = 0; y < options.height; y++) { for (size_t x = 0; x < options.width; x++) { const size_t index = x + y * options.width; - const auto& frag = tex[index]; + const auto& frag = texture[index]; // draw Fragment cout << colors[frag.color.col_4]; - cout << bars[frag.data]; + cout << dots[frag.data]; cout << colors[0]; } cout << '\n'; } } - // use a graph texture to draw a graph into the terminal - inline void draw(Texture& texture, const Options &options) { - graph(texture, options); - } - // print histogram using single data set, optionally drawn on top of existing texture + + // plot from single data set template - auto histogram_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}, Texture &&texture = std::make_unique()) -> Texture&& { - cout << "Experimental histogram\n"; - - // TODO: automatically set sizes if stuff is 0 - const double x_max = options.x.max; - const double x_min = options.x.min; - const double y_max = options.y.max; - const double y_min = options.y.min; - const size_t width = options.width; - const size_t height = options.height; - - // precalc graph span - const double x_size = x_max - x_min; - const double y_size = y_max - y_min; - // need width+height of chosen character set (using bar characters as example) - const size_t char_width = 1; // bar supports 1 state per character width - const size_t char_height = 8; // bar supports 8 states per character height - // calc how many data points we can represent with current character set and width/height - const size_t x_points = width * char_width; - const size_t y_points = height * char_height; - // for histograms, every sample needs at least one data point to occupy in x - const size_t x_bar_size = 1; - // given the x_bar_size (which should be exposed as an option), see how many points will be placed between bars - const size_t x_bar_spacer = (double)x_points / ((double)x_bar_size * x_size) - 1; - - // histogram specific: each bar needs at least one data point - if ((double)x_points / (double)x_bar_size < x_size) { - cerr << "width is too small to fit all histogram bars\n"; - return std::move(texture); - } - - // simple histogram as vector of sample counts - vector histogram(x_size, 0); - for (const auto &x: data) { - // check if value is between limits - if (x >= x_min && x < x_max) { - // calculate index on x-axis - const size_t index = x - x_min; - // increment height of the bar - ++histogram[index]; - } - } - - // create 2D array of temporary Fragments for the graph - if (texture->size() == 0) texture->resize(width * height); - assert(texture->size() == width * height); - vector& tex = *texture; - // insert draw histogram data into texture - for (size_t x = 0; x < histogram.size(); x++) { - // calc bar position on x-axis - const size_t x_pos = x * (x_bar_size + x_bar_spacer); - - // read bar size from histogram - const size_t y_histo = histogram[x]; - // scale to 0-1 range via max bar size - const double y_scaled = (double)y_histo / y_size; - // scale back up via point size - const size_t y_target = y_scaled * (double)y_points; - // calc Fragment position and remainder for cap - const size_t y_tex = y_target / char_height; - const size_t y_cap = y_target % char_height; - - // draw bar body - for (size_t y = 0; y < y_tex; y++) { - // in texture, y=0 is at the top, so we need to invert the y-axis - const size_t index = x_pos + (height - 1 - y) * width; - tex[index].color = color; - tex[index].data = 8; // use full height of character - } - - // draw bar cap - if (y_cap > 0) { - // in texture, y=0 is at the top, so we need to invert the y-axis - const size_t index = x_pos + (height - 1 - y_tex) * width; - tex[index].color = color; - tex[index].data = y_cap; // use remainder to fill up character - } - } - - if (options.draw_immediately) draw(texture, options); - return std::move(texture); - } - // print histogram using multiple data sets, drawn on top of existing texture - template - auto histogram_experimental(const T &data, const size_t rows, const Options &options = {}, const std::vector &colors = {}, Texture &&texture = std::make_unique()) -> Texture&& { - // recursively call for each data set - for (size_t row = 0; row < rows; row++) { - // pick default color if not enough colors are provided - Color color = row < colors.size() ? colors[row] : Color{color_red}; - texture = histogram_experimental(data[row], options, color, std::move(texture)); - } - return std::move(texture); - } - - // plot from single data set, optionally drawn on top of existing texture - template - auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}, Texture &&texture = std::make_unique()) -> Texture&& { + auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}) -> Intermediate { cout << "Experimental plot\n"; // precalc spans @@ -934,10 +845,9 @@ namespace graphs const long double x_span_recip = 1.0 / x_span; const long double y_span_recip = 1.0 / y_span; - // create 2D array of temporary Fragments for the graph - if (texture->size() == 0) texture->resize(options.width * options.height); - assert(texture->size() == options.width * options.height); - vector& tex = *texture; + // create new intermediate object for texture and options + assert(options.width > 0 && options.height > 0); // enforce valid size for now + Intermediate intermediate = { Texture(options.width * options.height), options }; // insert draw plot data into texture for (const auto [x, y]: data) { @@ -953,14 +863,48 @@ namespace graphs // draw Fragment const size_t index = (size_t)x_term + (size_t)y_term * options.width; // TODO: mix colors here when color is 24-bit (can we mix 8-bit color?) - tex[index].color = color; + intermediate.texture[index].color = color; // TODO: which bit should correspond to which braille dot? // the dot position within this fragment is (x_sub, y_sub) // bottom left is (0, 0), top right is (1, 3) - tex[index].data |= 1 << (x_sub + y_sub * 2); + intermediate.texture[index].data |= 1 << (x_sub + y_sub * 2); + } + } + return intermediate; + } + // plot from single data set, drawn on top of existing graph + template + void plot_experimental(const T &data, Intermediate &intermediate, const Color &color = {color_red}) { + cout << "Experimental plot\n"; + + // precalc spans + const Options& options = intermediate.options; + const long double x_span = options.x.max - options.x.min; + const long double y_span = options.y.max - options.y.min; + const long double x_span_recip = 1.0 / x_span; + const long double y_span_recip = 1.0 / y_span; + + // insert draw plot data into texture + for (const auto [x, y]: data) { + // check if value is between limits + if (x >= options.x.min && x < options.x.max && y >= options.y.min && y < options.y.max) { + // calculate terminal character position + const long double x_term = ((long double)x - options.x.min) * x_span_recip * (long double)options.width; + const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; + // calculate sub-fragment position (2x4 for braille) + const size_t x_sub = (x_term - std::floor(x_term)) * 2; + const size_t y_sub = (y_term - std::floor(y_term)) * 4; + + // draw Fragment + const size_t index = (size_t)x_term + (size_t)y_term * options.width; + // TODO: mix colors here when color is 24-bit (can we mix 8-bit color?) + intermediate.texture[index].color = color; + // TODO: which bit should correspond to which braille dot? + // the dot position within this fragment is (x_sub, y_sub) + // bottom left is (0, 0), top right is (1, 3) + intermediate.texture[index].data |= 1 << (x_sub + y_sub * 2); } } - return std::move(texture); } // EXPERIMENTAL END From 42e9d737191efe37cf9b9113b11b85e23773febc Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:54:31 +0100 Subject: [PATCH 11/20] move graphing into intermediate --- graphs.hpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 8458203..e44005e 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -806,34 +806,28 @@ namespace graphs bool border = false; // draw border around the graph bool draw_immediately = true; // draw graph immediately after creation. otherwise call draw/graph with the returned texture }; - inline void graph(Texture& texture, const Options &options); // intermediate representation of a graph texture for ease of passing around struct Intermediate { // use a graph texture to draw into the terminal inline void draw() { - graph(texture, options); + // draw graph for experimental preview purposes only + for (size_t y = 0; y < options.height; y++) { + for (size_t x = 0; x < options.width; x++) { + const size_t index = x + y * options.width; + const auto& frag = texture[index]; + // draw Fragment + cout << colors[frag.color.col_4]; + cout << dots[frag.data]; + cout << colors[0]; + } + cout << '\n'; + } } Texture texture; const Options options; }; - // use a graph texture to draw a graph into the terminal - inline void graph(Texture& texture, const Options &options) { - // draw graph for experimental preview purposes only - for (size_t y = 0; y < options.height; y++) { - for (size_t x = 0; x < options.width; x++) { - const size_t index = x + y * options.width; - const auto& frag = texture[index]; - // draw Fragment - cout << colors[frag.color.col_4]; - cout << dots[frag.data]; - cout << colors[0]; - } - cout << '\n'; - } - } - // plot from single data set template auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}) -> Intermediate { From f9ff88c27644d7c10a9b01fb28111605e959fed6 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:40:20 +0100 Subject: [PATCH 12/20] fixed braille rendering --- graphs.hpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index e44005e..b1c6c69 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -1,5 +1,6 @@ // Teal Dulcet, CS546 #pragma once +#include #include #include #include @@ -789,10 +790,10 @@ namespace graphs struct Options { size_t width = 0; // Width in terminal characters. Set to 0 for automatic size based on terminal. size_t height = 0; // Height in terminal characters. Set to 0 for automatic size based on terminal. - + Axis x = {}; Axis y = {}; - + type_type character_set = type_braille; plot_type plot = plot_scatter; style_type style = style_light; @@ -843,25 +844,22 @@ namespace graphs assert(options.width > 0 && options.height > 0); // enforce valid size for now Intermediate intermediate = { Texture(options.width * options.height), options }; - // insert draw plot data into texture + // draw plot data into texture for (const auto [x, y]: data) { // check if value is between limits if (x >= options.x.min && x < options.x.max && y >= options.y.min && y < options.y.max) { // calculate terminal character position const long double x_term = ((long double)x - options.x.min) * x_span_recip * (long double)options.width; const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; + // calculate sub-fragment position (2x4 for braille) const size_t x_sub = (x_term - std::floor(x_term)) * 2; const size_t y_sub = (y_term - std::floor(y_term)) * 4; // draw Fragment - const size_t index = (size_t)x_term + (size_t)y_term * options.width; - // TODO: mix colors here when color is 24-bit (can we mix 8-bit color?) - intermediate.texture[index].color = color; - // TODO: which bit should correspond to which braille dot? - // the dot position within this fragment is (x_sub, y_sub) - // bottom left is (0, 0), top right is (1, 3) - intermediate.texture[index].data |= 1 << (x_sub + y_sub * 2); + const size_t index = (size_t)x_term + (options.height - 1 - (size_t)y_term) * options.width; + intermediate.texture[index].color = color; // TODO: mix color here + intermediate.texture[index].data |= dotvalues[x_sub][3-y_sub]; } } return intermediate; @@ -885,18 +883,15 @@ namespace graphs // calculate terminal character position const long double x_term = ((long double)x - options.x.min) * x_span_recip * (long double)options.width; const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; + // calculate sub-fragment position (2x4 for braille) const size_t x_sub = (x_term - std::floor(x_term)) * 2; const size_t y_sub = (y_term - std::floor(y_term)) * 4; // draw Fragment - const size_t index = (size_t)x_term + (size_t)y_term * options.width; - // TODO: mix colors here when color is 24-bit (can we mix 8-bit color?) - intermediate.texture[index].color = color; - // TODO: which bit should correspond to which braille dot? - // the dot position within this fragment is (x_sub, y_sub) - // bottom left is (0, 0), top right is (1, 3) - intermediate.texture[index].data |= 1 << (x_sub + y_sub * 2); + const size_t index = (size_t)x_term + (options.height - 1 - (size_t)y_term) * options.width; + intermediate.texture[index].color = color; // TODO: mix color here + intermediate.texture[index].data |= dotvalues[x_sub][3-y_sub]; } } } From ebcbc6978d3435d0b5b5201131dc2fcec2633941 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:44:42 +0100 Subject: [PATCH 13/20] properly invert sub-fragment height --- graphs.hpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index b1c6c69..7ceb6cb 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -853,13 +853,17 @@ namespace graphs const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; // calculate sub-fragment position (2x4 for braille) - const size_t x_sub = (x_term - std::floor(x_term)) * 2; - const size_t y_sub = (y_term - std::floor(y_term)) * 4; + const size_t char_width = 2; + const size_t char_height = 4; + size_t x_sub = (x_term - std::floor(x_term)) * char_width; + size_t y_sub = (y_term - std::floor(y_term)) * char_height; + // invert y_sub + y_sub = char_height - 1 - y_sub; // draw Fragment const size_t index = (size_t)x_term + (options.height - 1 - (size_t)y_term) * options.width; intermediate.texture[index].color = color; // TODO: mix color here - intermediate.texture[index].data |= dotvalues[x_sub][3-y_sub]; + intermediate.texture[index].data |= dotvalues[x_sub][y_sub]; } } return intermediate; @@ -885,13 +889,17 @@ namespace graphs const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; // calculate sub-fragment position (2x4 for braille) - const size_t x_sub = (x_term - std::floor(x_term)) * 2; - const size_t y_sub = (y_term - std::floor(y_term)) * 4; + const size_t char_width = 2; + const size_t char_height = 4; + size_t x_sub = (x_term - std::floor(x_term)) * char_width; + size_t y_sub = (y_term - std::floor(y_term)) * char_height; + // invert y_sub + y_sub = char_height - 1 - y_sub; // draw Fragment const size_t index = (size_t)x_term + (options.height - 1 - (size_t)y_term) * options.width; intermediate.texture[index].color = color; // TODO: mix color here - intermediate.texture[index].data |= dotvalues[x_sub][3-y_sub]; + intermediate.texture[index].data |= dotvalues[x_sub][y_sub]; } } } From 353b46c2e971116139cd4f6d0d9b44f5e0ccf808 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:00:34 +0100 Subject: [PATCH 14/20] added block char set --- graphs.hpp | 95 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 7ceb6cb..b9d8c6a 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -818,7 +818,15 @@ namespace graphs const auto& frag = texture[index]; // draw Fragment cout << colors[frag.color.col_4]; - cout << dots[frag.data]; + switch (options.character_set) { + case type_braille: + cout << dots[frag.data]; + break; + case type_block: + cout << blocks[frag.data]; + break; + default: break; + } cout << colors[0]; } cout << '\n'; @@ -829,45 +837,6 @@ namespace graphs const Options options; }; - // plot from single data set - template - auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}) -> Intermediate { - cout << "Experimental plot\n"; - - // precalc spans - const long double x_span = options.x.max - options.x.min; - const long double y_span = options.y.max - options.y.min; - const long double x_span_recip = 1.0 / x_span; - const long double y_span_recip = 1.0 / y_span; - - // create new intermediate object for texture and options - assert(options.width > 0 && options.height > 0); // enforce valid size for now - Intermediate intermediate = { Texture(options.width * options.height), options }; - - // draw plot data into texture - for (const auto [x, y]: data) { - // check if value is between limits - if (x >= options.x.min && x < options.x.max && y >= options.y.min && y < options.y.max) { - // calculate terminal character position - const long double x_term = ((long double)x - options.x.min) * x_span_recip * (long double)options.width; - const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; - - // calculate sub-fragment position (2x4 for braille) - const size_t char_width = 2; - const size_t char_height = 4; - size_t x_sub = (x_term - std::floor(x_term)) * char_width; - size_t y_sub = (y_term - std::floor(y_term)) * char_height; - // invert y_sub - y_sub = char_height - 1 - y_sub; - - // draw Fragment - const size_t index = (size_t)x_term + (options.height - 1 - (size_t)y_term) * options.width; - intermediate.texture[index].color = color; // TODO: mix color here - intermediate.texture[index].data |= dotvalues[x_sub][y_sub]; - } - } - return intermediate; - } // plot from single data set, drawn on top of existing graph template void plot_experimental(const T &data, Intermediate &intermediate, const Color &color = {color_red}) { @@ -889,8 +858,20 @@ namespace graphs const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; // calculate sub-fragment position (2x4 for braille) - const size_t char_width = 2; - const size_t char_height = 4; + size_t char_width = 0; + size_t char_height = 0; + // TODO: could put this in a separate function to avoid switch statement and reuse in other plot funcs + switch (options.character_set) { + case type_braille: + char_width = 2; + char_height = 4; + break; + case type_block: + char_width = 2; + char_height = 2; + break; + default: break; + } size_t x_sub = (x_term - std::floor(x_term)) * char_width; size_t y_sub = (y_term - std::floor(y_term)) * char_height; // invert y_sub @@ -899,10 +880,38 @@ namespace graphs // draw Fragment const size_t index = (size_t)x_term + (options.height - 1 - (size_t)y_term) * options.width; intermediate.texture[index].color = color; // TODO: mix color here - intermediate.texture[index].data |= dotvalues[x_sub][y_sub]; + + uint8_t value = 0; + switch (options.character_set) { + case type_braille: + value = dotvalues[x_sub][y_sub]; + break; + case type_block: + value = blockvalues[x_sub][y_sub]; + break; + default: break; + } + intermediate.texture[index].data |= value; } } } + // plot from single data set + template + auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}) -> Intermediate { + cout << "Experimental plot\n"; + + // precalc spans + const long double x_span = options.x.max - options.x.min; + const long double y_span = options.y.max - options.y.min; + const long double x_span_recip = 1.0 / x_span; + const long double y_span_recip = 1.0 / y_span; + + // create new intermediate object for texture and options + assert(options.width > 0 && options.height > 0); // enforce valid size for now + Intermediate intermediate = { Texture(options.width * options.height), options }; + plot_experimental(data, intermediate, color); + return intermediate; + } // EXPERIMENTAL END template From 6cfa086a2e221855c39508e648a30845133c26b0 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:02:35 +0100 Subject: [PATCH 15/20] respect immediate draw --- graphs.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graphs.hpp b/graphs.hpp index b9d8c6a..cb2fab3 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -894,6 +894,11 @@ namespace graphs intermediate.texture[index].data |= value; } } + + // draw plot into terminal immediately if requested + if (options.draw_immediately) { + intermediate.draw(); + } } // plot from single data set template From cf7b07a8811a3f449e12afa7659a8c81eeb99ca1 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:19:31 +0100 Subject: [PATCH 16/20] adjusted block/color to changes on master --- graphs.hpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 317a812..9dc060c 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -874,18 +874,20 @@ namespace graphs for (size_t x = 0; x < options.width; x++) { const size_t index = x + y * options.width; const auto& frag = texture[index]; - // draw Fragment - cout << colors[frag.color.col_4]; + // begin color draw( + cout << outputcolor(frag.color.col_4); + // draw character switch (options.character_set) { case type_braille: cout << dots[frag.data]; break; - case type_block: - cout << blocks[frag.data]; + case type_block_quadrant: + cout << blocks_quadrant[frag.data]; break; default: break; } - cout << colors[0]; + // reset color (TODO: could be optimized to only reset when color changes) + cout << outputcolor(color_type::color_default); } cout << '\n'; } @@ -924,7 +926,7 @@ namespace graphs char_width = 2; char_height = 4; break; - case type_block: + case type_block_quadrant: char_width = 2; char_height = 2; break; @@ -940,12 +942,13 @@ namespace graphs intermediate.texture[index].color = color; // TODO: mix color here uint8_t value = 0; + // TODO: put this in separate function to reuse in other plot funcs switch (options.character_set) { case type_braille: value = dotvalues[x_sub][y_sub]; break; - case type_block: - value = blockvalues[x_sub][y_sub]; + case type_block_quadrant: + value = 1 << (x_sub + y_sub * char_width); break; default: break; } From 3e665661f31d049d7a96371299885e7e4398cf9c Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:54:19 +0100 Subject: [PATCH 17/20] passing colors as copy --- graphs.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 9dc060c..2257c21 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -899,7 +899,7 @@ namespace graphs // plot from single data set, drawn on top of existing graph template - void plot_experimental(const T &data, Intermediate &intermediate, const Color &color = {color_red}) { + void plot_experimental(const T &data, Intermediate &intermediate, Color color = {color_red}) { cout << "Experimental plot\n"; // precalc spans @@ -963,7 +963,7 @@ namespace graphs } // plot from single data set template - auto plot_experimental(const T &data, const Options &options = {}, const Color &color = {color_red}) -> Intermediate { + auto plot_experimental(const T &data, const Options &options = {}, Color color = {color_red}) -> Intermediate { cout << "Experimental plot\n"; // precalc spans From fe16c101cf7c05aca764a6d333f9628cf90241ea Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:59:25 +0100 Subject: [PATCH 18/20] update options with axis draw flag --- graphs.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs.hpp b/graphs.hpp index 2257c21..7263650 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -840,6 +840,7 @@ namespace graphs struct Axis { long double min = 0; long double max = 0; + bool drawn = true; bool labels = true; bool ticks = true; bool units_label = true; From e0836b13a3ee95d5fc89d6b4842f1651da751552 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:00:16 +0100 Subject: [PATCH 19/20] removed leftover const vars --- graphs.hpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index 7263650..e6f77ab 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -966,13 +966,7 @@ namespace graphs template auto plot_experimental(const T &data, const Options &options = {}, Color color = {color_red}) -> Intermediate { cout << "Experimental plot\n"; - - // precalc spans - const long double x_span = options.x.max - options.x.min; - const long double y_span = options.y.max - options.y.min; - const long double x_span_recip = 1.0 / x_span; - const long double y_span_recip = 1.0 / y_span; - + // create new intermediate object for texture and options assert(options.width > 0 && options.height > 0); // enforce valid size for now Intermediate intermediate = { Texture(options.width * options.height), options }; From afdd9a597b45949eab551fbacdc27f71f5ce2438 Mon Sep 17 00:00:00 2001 From: Jan Kuhlmann <33833587+M2-TE@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:03:20 +0100 Subject: [PATCH 20/20] using density array to get character dimensions --- graphs.hpp | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/graphs.hpp b/graphs.hpp index e6f77ab..e473a7b 100644 --- a/graphs.hpp +++ b/graphs.hpp @@ -919,20 +919,7 @@ namespace graphs const long double y_term = ((long double)y - options.y.min) * y_span_recip * (long double)options.height; // calculate sub-fragment position (2x4 for braille) - size_t char_width = 0; - size_t char_height = 0; - // TODO: could put this in a separate function to avoid switch statement and reuse in other plot funcs - switch (options.character_set) { - case type_braille: - char_width = 2; - char_height = 4; - break; - case type_block_quadrant: - char_width = 2; - char_height = 2; - break; - default: break; - } + const auto [char_width, char_height] = densities[options.character_set]; size_t x_sub = (x_term - std::floor(x_term)) * char_width; size_t y_sub = (y_term - std::floor(y_term)) * char_height; // invert y_sub @@ -966,7 +953,7 @@ namespace graphs template auto plot_experimental(const T &data, const Options &options = {}, Color color = {color_red}) -> Intermediate { cout << "Experimental plot\n"; - + // create new intermediate object for texture and options assert(options.width > 0 && options.height > 0); // enforce valid size for now Intermediate intermediate = { Texture(options.width * options.height), options };