#include "canvas_graph.hpp" #include using namespace emscripten; struct Line { double x1; double y1; double x2; double y2; }; static void draw_lines(emscripten::val& ctx, const std::vector& lines, const char* style, double width) { ctx.set("strokeStyle", val(style)); ctx.set("lineWidth", val(width)); ctx.call("beginPath"); for (const Line& line : lines) { ctx.call("moveTo", val(std::floor(line.x1 + 0.5)), val(std::floor(line.y1 + 0.5))); ctx.call("lineTo", val(std::floor(line.x2 + 0.5)), val(std::floor(line.y2 + 0.5))); } ctx.call("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 origin_lines; static std::vector bold_grid_lines; static std::vector 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* lines = &fine_grid_lines; if (i % 5 == 0) { if (i == 0) { lines = &origin_lines; } else { lines = &bold_grid_lines; } ctx.call("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* lines = &fine_grid_lines; if (i % 5 == 0) { if (i == 0) { lines = &origin_lines; } else { lines = &bold_grid_lines; } ctx.call("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("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(cmd, val(x_canvas), val(y_canvas)); } ctx.call("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("getElementById", val("canvas")); val ctx = canvas.call("getContext", val("2d")); double canvas_width = canvas["width"].as(); double canvas_height = canvas["height"].as(); // clear canvas ctx.call("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::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("beginPath"); ctx.call("arc", val(pointer_x), val(closest_y_canvas), val(2.5), val(0), val(2 * M_PI)); ctx.call("fill"); ctx.call("fillText", val(text.c_str()), val(pointer_x), val(closest_y_canvas - 5)); } }