extern "C" { #include "canvas_graph.h" } #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.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 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 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("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("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)); draw_grid(ctx, range, canvas_width, canvas_height); // ctx.set("strokeStyle", val("#888888")); // ctx.set("lineWidth", val(1.0)); // ctx.call("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("moveTo", val(x_canvas), val(0)); // ctx.call("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("moveTo", val(0), val(y_canvas)); // ctx.call("lineTo", val(canvas_width), val(y_canvas)); // } // ctx.call("stroke"); // graph ctx.set("strokeStyle", val("#000088")); ctx.set("lineWidth", val(2.0)); 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); 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(cmd, val(x_canvas), val(y_canvas)); } ctx.call("stroke"); }