227 lines
6.8 KiB
C++
Executable File
227 lines
6.8 KiB
C++
Executable File
#include "canvas_graph.hpp"
|
|
|
|
#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.02) / std::log(log_base)));
|
|
|
|
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 draw_expr_graph(emscripten::val& ctx, const Expression& expr, const struct graph_range *range, const char* style, double width, double canvas_width, double canvas_height) {
|
|
|
|
ctx.set("strokeStyle", val(style));
|
|
ctx.set("lineWidth", val(width));
|
|
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);
|
|
bool res = expr.Evaluate(x, y);
|
|
|
|
if (!res) {
|
|
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");
|
|
}
|
|
|
|
const char* const colors[] = {
|
|
"#000088",
|
|
"#880000",
|
|
"#008800",
|
|
"#880088",
|
|
"#008888",
|
|
"#888800",
|
|
"#000000",
|
|
"#888888",
|
|
"#444444",
|
|
"#ff0000",
|
|
"#00ff00",
|
|
"#0000ff",
|
|
"#ff00ff",
|
|
"#00ffff",
|
|
"#ffff00",
|
|
"#ffffff",
|
|
};
|
|
|
|
const size_t color_count = sizeof(colors) / sizeof(colors[0]);
|
|
|
|
#define IDX_NOT_FOUND SIZE_MAX
|
|
|
|
void canvas_generate_graph(const Expression* expr, size_t expr_count, const struct graph_range *range, double pointer_x, double pointer_y) {
|
|
const val document = val::global("document");
|
|
|
|
|
|
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));
|
|
|
|
ctx.set("font", val("12px Arial"));
|
|
|
|
draw_grid(ctx, range, canvas_width, canvas_height);
|
|
|
|
size_t closest_expr_idx = IDX_NOT_FOUND;
|
|
double closest_dist = std::numeric_limits<double>::max();
|
|
double closest_y_canvas = 0;
|
|
double closest_x_value = 0;
|
|
double closest_y_value = 0;
|
|
|
|
for (size_t i = 0; i < expr_count; ++i) {
|
|
if (!expr[i].IsValid()) {
|
|
continue;
|
|
}
|
|
|
|
double x = pointer_x * (range->xmax - range->xmin) / canvas_width + range->xmin;
|
|
double y;
|
|
bool res = expr[i].Evaluate(x, y);
|
|
|
|
if (!res) {
|
|
continue;
|
|
}
|
|
|
|
double y_canvas = (range->ymax - y) / (range->ymax - range->ymin) * canvas_height;
|
|
double dist = std::abs(y_canvas - pointer_y);
|
|
|
|
if (dist > 50) {
|
|
continue;
|
|
}
|
|
|
|
if (dist < closest_dist) {
|
|
closest_dist = dist;
|
|
closest_expr_idx = i;
|
|
closest_y_canvas = y_canvas;
|
|
closest_x_value = x;
|
|
closest_y_value = y;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < expr_count; ++i) {
|
|
if (!expr[i].IsValid()) {
|
|
continue;
|
|
}
|
|
|
|
double width = 2.0;
|
|
if (i == closest_expr_idx) {
|
|
width = 2.5;
|
|
}
|
|
|
|
draw_expr_graph(ctx, expr[i], range, colors[i % color_count], width, canvas_width, canvas_height);
|
|
}
|
|
|
|
if (closest_expr_idx != IDX_NOT_FOUND) {
|
|
std::string text = "f(" + std::to_string(closest_x_value) + ") = " + std::to_string(closest_y_value);
|
|
ctx.set("fillStyle", val("#000000"));
|
|
ctx.call<void>("beginPath");
|
|
ctx.call<void>("arc", val(pointer_x), val(closest_y_canvas), val(2.5), val(0), val(2 * M_PI));
|
|
ctx.call<void>("fill");
|
|
ctx.call<void>("fillText", val(text.c_str()), val(pointer_x), val(closest_y_canvas - 5));
|
|
}
|
|
} |