Skip to content
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

Intermediate representation for layered draw #9

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
150 changes: 149 additions & 1 deletion graphs.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Teal Dulcet, CS546
#pragma once
#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <cstring>
#include <cmath>
Expand Down Expand Up @@ -54,7 +56,7 @@ namespace graphs
// {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "} // No border
};

enum color_type
enum color_type: uint8_t
{
color_default,
color_black,
Expand Down Expand Up @@ -754,6 +756,152 @@ 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 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
};
// 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() {
// 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;
};

// plot from single data set
template <typename T>
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 };

// 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 intermediate;
}
// plot from single data set, drawn on top of existing graph
template <typename T>
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);
}
}
}
// 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