GraphWeb/canvas_graph.cpp
2025-01-31 14:54:52 +01:00

186 lines
6.0 KiB
C++
Executable File

extern "C" {
#include "canvas_graph.h"
}
#include <emscripten/val.h>
using namespace emscripten;
struct Line {
double x1;
double y1;
double x2;
double y2;
};
static void draw_lines(emscripten::val& ctx, const std::vector<Line>& lines, const char* style, double width) {
ctx.set("strokeStyle", val(style));
ctx.set("lineWidth", val(width));
ctx.call<void>("beginPath");
for (const Line& line : lines) {
ctx.call<void>("moveTo", val(std::floor(line.x1 + 0.5)), val(std::floor(line.y1 + 0.5)));
ctx.call<void>("lineTo", val(std::floor(line.x2 + 0.5)), val(std::floor(line.y2 + 0.5)));
}
ctx.call<void>("stroke");
}
void draw_grid(emscripten::val& ctx, const struct graph_range *range, double canvas_width, double canvas_height) {
// grid
double big_range = std::max(range->xmax - range->xmin, range->ymax - range->ymin);
double log_base = 2.0;
double grid_size = std::pow(log_base, std::floor(std::log(big_range * 0.05) / std::log(log_base)));
// double grid_size = std::pow(10.0, std::floor(std::log10(big_range * 0.1)));
// if (grid_size < 0.1)
// grid_size = 0.1;
static std::vector<Line> origin_lines;
static std::vector<Line> bold_grid_lines;
static std::vector<Line> fine_grid_lines;
origin_lines.clear();
bold_grid_lines.clear();
fine_grid_lines.clear();
double x_labels_y = range->ymax / (range->ymax - range->ymin) * canvas_height + 10;
if (x_labels_y > canvas_height - 10)
x_labels_y = canvas_height - 10;
else if (x_labels_y < 10)
x_labels_y = 10;
for (int i = range->xmin / grid_size; i <= range->xmax / grid_size; ++i) {
double x = i * grid_size;
double x_canvas = (x - range->xmin) / (range->xmax - range->xmin) * canvas_width;
std::vector<Line>* lines = &fine_grid_lines;
if (i % 5 == 0) {
if (i == 0) {
lines = &origin_lines;
} else {
lines = &bold_grid_lines;
}
ctx.call<void>("fillText", val(x), val(x_canvas + 2), val(x_labels_y));
}
lines->push_back({x_canvas, 0, x_canvas, canvas_height});
}
double y_labels_x = -range->xmin / (range->xmax - range->xmin) * canvas_width;
if (y_labels_x > canvas_width - 10)
y_labels_x = canvas_width - 10;
else if (y_labels_x < 10)
y_labels_x = 10;
for (int i = range->ymin / grid_size; i <= range->ymax / grid_size; ++i) {
double y = i * grid_size;
double y_canvas = (range->ymax - y) / (range->ymax - range->ymin) * canvas_height;
std::vector<Line>* lines = &fine_grid_lines;
if (i % 5 == 0) {
if (i == 0) {
lines = &origin_lines;
} else {
lines = &bold_grid_lines;
}
ctx.call<void>("fillText", val(y), val(y_labels_x + 2), val(y_canvas - 2));
}
lines->push_back({0, y_canvas, canvas_width, y_canvas});
}
draw_lines(ctx, fine_grid_lines, "#dddddd", 1.0);
draw_lines(ctx, bold_grid_lines, "#444444", 1.0);
draw_lines(ctx, origin_lines, "#000000", 1.5);
}
void canvas_generate_graph(const struct expr_node *node, const struct graph_range *range, const char *function) {
const val document = val::global("document");
// val temp = document.call<val>("getElementById", val("temp"));
// std::string out;
// for (int i = -10; i <= 10; ++i) {
// double x = i * 0.1;
// double y;
// eval_result res = node_eval(node, x, &y);
// if (res == EVAL_OK) {
// out += std::to_string(y);
// out += "\n";
// }
// }
// temp.set("innerText", out);
val canvas = document.call<val>("getElementById", val("canvas"));
val ctx = canvas.call<val>("getContext", val("2d"));
double canvas_width = canvas["width"].as<double>();
double canvas_height = canvas["height"].as<double>();
// clear canvas
ctx.call<void>("clearRect", val(0), val(0), val(canvas_width), val(canvas_height));
draw_grid(ctx, range, canvas_width, canvas_height);
// ctx.set("strokeStyle", val("#888888"));
// ctx.set("lineWidth", val(1.0));
// ctx.call<void>("beginPath");
// double first_grid_x = (int)(range->xmin / grid_size) * grid_size;
// double grid_x;
// for (int i = 0; (grid_x = first_grid_x + i * grid_size) <= range->xmax; ++i) {
// double x_canvas = (grid_x - range->xmin) / (range->xmax - range->xmin) * canvas_width;
// ctx.call<void>("moveTo", val(x_canvas), val(0));
// ctx.call<void>("lineTo", val(x_canvas), val(canvas_height));
// }
// double first_grid_y = (int)(range->ymin / grid_size) * grid_size;
// double grid_y;
// for (int i = 0; (grid_y = first_grid_y + i * grid_size) <= range->ymax; ++i) {
// double y_canvas = (range->ymax - grid_y) / (range->ymax - range->ymin) * canvas_height;
// ctx.call<void>("moveTo", val(0), val(y_canvas));
// ctx.call<void>("lineTo", val(canvas_width), val(y_canvas));
// }
// ctx.call<void>("stroke");
// graph
ctx.set("strokeStyle", val("#000088"));
ctx.set("lineWidth", val(2.0));
ctx.call<void>("beginPath");
double segment_size = (range->xmax - range->xmin) / 1000.0;
double first_x = (int)(range->xmin / segment_size) * segment_size;
double x;
bool use_moveto = true;
for (int i = 0; (x = first_x + i * segment_size) <= range->xmax; ++i) {
double y;
eval_result res = node_eval(node, x, &y);
if (res != EVAL_OK) {
use_moveto = true;
continue;
}
double x_canvas = (x - range->xmin) / (range->xmax - range->xmin) * canvas_width;
double y_canvas = (range->ymax - y) / (range->ymax - range->ymin) * canvas_height;
const char* cmd = "lineTo";
if (use_moveto) {
cmd = "moveTo";
use_moveto = false;
}
ctx.call<void>(cmd, val(x_canvas), val(y_canvas));
}
ctx.call<void>("stroke");
}