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] 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 = {}) {