Skip to content

Intermediate representation for layered draw #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 151 additions & 1 deletion graphs.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Teal Dulcet, CS546
#pragma once
#include <bitset>
#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <cstring>
#include <cmath>
Expand Down Expand Up @@ -54,7 +57,7 @@ namespace graphs
// {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "} // No border
};

enum color_type
enum color_type: uint8_t
{
color_default,
color_black,
Expand Down Expand Up @@ -812,6 +815,153 @@ namespace graphs
return 0;
}

// EXPERIMENTAL BEG

// 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 red;
uint8_t green;
uint8_t blue;
} 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
};
// store fragments on temporary buffer to pass between draws
using Texture = std::vector<Fragment>;
struct Axis {
long double min = 0;
long double max = 0;
bool drawn = true;
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.

Axis x = {};
Axis y = {};

type_type character_set = type_braille;
plot_type plot = plot_scatter;
style_type style = style_light;
graph_type graph = graph_dot;

std::string title;
std::ostream &ostr = std::cout;

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
};
Comment on lines +849 to +868
Copy link
Owner

@tdulcet tdulcet Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new Option struct is missing some of the existing options. See #9 (comment) above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was only missing bool axis when compared to the Options there right? I have added bool drawn = true to the Axis struct for that one

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are missing the mark option as well. The plot and graph options are currently unused.

Interesting about allowing the axes to be enabled independently. That will require some changes to the graph function.

// 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() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am guessing you would want to eventually replace this with the existing graph function, of course updating it to accept an Intermediate struct instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup that's the idea. I didn't wanna taint any functions outside my experimental area yet, so I put it into the Intermediate itself.

// 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];
// begin color draw(
cout << outputcolor(frag.color.col_4);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably update the outputcolor function to accept the entire Color struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would need to pass the color bits as well, so the signature could look something like this?

inline string outputcolor(Color color, ColorBits color_bits)

Makes sense to copy, rather than passing references

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me! I usually keep the const quantifier unless of course I am planning to modify the argument values.

// draw character
switch (options.character_set) {
case type_braille:
cout << dots[frag.data];
break;
case type_block_quadrant:
cout << blocks_quadrant[frag.data];
break;
default: break;
}
// reset color (TODO: could be optimized to only reset when color changes)
cout << outputcolor(color_type::color_default);
}
cout << '\n';
}
}

Texture texture;
const Options options;
};

// plot from single data set, drawn on top of existing graph
template <typename T>
void plot_experimental(const T &data, Intermediate &intermediate, 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 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
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

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_quadrant:
value = 1 << (x_sub + y_sub * char_width);
break;
default: break;
}
intermediate.texture[index].data |= value;
}
}

// draw plot into terminal immediately if requested
if (options.draw_immediately) {
intermediate.draw();
}
}
// plot from single data set
template <typename T>
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 };
plot_experimental(data, intermediate, color);
return intermediate;
}
// EXPERIMENTAL END

template <typename T>
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 = {})
{
Expand Down
Loading