Untitled
unknown
javascript
3 years ago
7.8 kB
36
Indexable
import {ticks, format_value} from './util.js'; import {parse} from './evaluator/parse.js'; import {build_expression} from './evaluator/expression.js'; const container = document.querySelector(".container"); const error_box = document.querySelector(".error"); const canvas = document.querySelector("canvas"); const min_max_button = document.querySelector(".min_max_button"); const draw_button = document.querySelector("#evaluate"); const expression_input = document.querySelector("#expression"); const min_domain_input = document.querySelector("#x_min"); const max_domain_input = document.querySelector("#x_max"); const form = document.querySelector('form'); const ORIGIN_OFFSET = 60; form.addEventListener('submit', function(event) { event.preventDefault(); clear(); if(compute()) draw(); }); draw_button.addEventListener('click', function() { clear(); if(compute()) draw(); }) min_max_button.addEventListener('click', function() { if(min_max_button.getAttribute('class') === 'min_max_button minimise') { min_max_button.setAttribute('class', 'min_max_button'); container.setAttribute('class', 'container'); } else { min_max_button.setAttribute('class', 'min_max_button minimise'); container.setAttribute('class', 'container maximised'); } window_resize(); clear(); draw(); }) window.addEventListener('resize',function() { window_resize(); clear(); draw(); }) window.addEventListener('load', (event) => { let url_args = window.location.search.slice(1); let params = url_args.split('&') if(params.length === 3 && params[1].startsWith('start=') && params[2].startsWith('end=')) { expression_input.value = params[0]; min_domain_input.value = params[1].slice(6); max_domain_input.value = params[2].slice(4); } else { expression_input.value = 'x'; min_domain_input.value = '-1.0'; max_domain_input.value = '1.0'; } compute(); draw(); }) const X = new Array(100); const Y = new Array(100); for(let i = 0; i < X.length; i++) { X[i] = 0.01*i; } class ToCanvas { constructor(dmin, dmax, rmin, rmax) { this.dmin = dmin; this.dmax = dmax; this.rmin = rmin; this.rmax = rmax; } convert(value) { return this.rmin + (this.rmax - this.rmin) * (value - this.dmin) / (this.dmax - this.dmin); } } function range(arr) { let max = Number.MIN_VALUE; let min = Number.MAX_VALUE; for(let item of arr) { max = (item > max) ? item : max; min = (item < min) ? item : min; } return [min, max]; } function window_resize() { let ratio = window.devicePixelRatio; canvas.width = canvas.offsetWidth * ratio; canvas.height = canvas.offsetHeight * ratio; } let x_ticks = [] let y_ticks = [] function _compute_with_possible_errors() { let X_min = Number(min_domain_input.value); let X_max = Number(max_domain_input.value); let X_range = X_max - X_min; if(X_range <= 0) { throw new Error("Range of domain must be > 0"); } let tokens = parse(expression_input.value); console.log(tokens); let exp = build_expression(tokens); console.log(exp.toString()); let evaluation_error = '' for(let i = 0; i < X.length; i++) { try { X[i] = X_min + (X_range)*(i/100); Y[i] = exp.evaluate({x: X[i]}); } catch (e) { evaluation_error = e.name + ': ' + e.message; console.error(evaluation_error); Y[i] = Number.NaN; } } if(Y.every(x => Number.isNaN(x))) { throw new Error(evaluation_error); } let [Y_min, Y_max] = range(Y); y_ticks = ticks(Y_min, Y_max); x_ticks = ticks(X_min, X_max); console.log(x_ticks); console.log(y_ticks); } function compute() { try { _compute_with_possible_errors(); error_box.textContent = ''; return true; } catch(e) { if(e.message.toString().startsWith('Error:')) { e.message = e.message.slice(6); } x_ticks = [] y_ticks = [] error_box.textContent = e.name + ': ' + e.message; console.log(e.name + ': ' + e.message); } return false; } function clear() { let ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); } function draw() { let ctx = canvas.getContext("2d"); ctx.lineWidth = 1; ctx.strokeStyle = 'rgb(0,0,0)'; ctx.font = '15px sans-serif'; let [X_min, X_max] = range(X); let [Y_min, Y_max] = range(Y); let cx = new ToCanvas(X_min, X_max, ORIGIN_OFFSET, canvas.width); let cy = new ToCanvas(Y_min, Y_max, ORIGIN_OFFSET, canvas.height); // Axes ctx.beginPath(); ctx.moveTo(cx.convert(X_min), canvas.height - cy.convert(Y_min)); ctx.lineTo(cx.convert(X_max), canvas.height - cy.convert(Y_min)); ctx.moveTo(cx.convert(X_min), canvas.height - cy.convert(Y_min)); ctx.lineTo(cx.convert(X_min), canvas.height - cy.convert(Y_max)); // Axes Labels let x_axes_y = canvas.height - cy.convert(Y_min); for(let tick of x_ticks) { // TODO: Don't have this tick to start with! if(tick < X_min) { continue; } let label = format_value(tick); let label_width = ctx.measureText(label).width; ctx.fillText(label, cx.convert(tick) - label_width/2, x_axes_y + 20); ctx.moveTo(cx.convert(tick), x_axes_y + 5); ctx.lineTo(cx.convert(tick), x_axes_y); } let y_axes_x = cx.convert(X_min); // TODO: Use label height and width for(let tick of y_ticks) { // TODO: Don't have this tick to start with! if(tick < Y_min) { continue; } let label = format_value(tick); let label_width = ctx.measureText(label).width; ctx.fillText(label, y_axes_x - label_width - 8, canvas.height - cy.convert(tick) + 5); ctx.moveTo(y_axes_x - 5, canvas.height - cy.convert(tick)); ctx.lineTo(y_axes_x, canvas.height - cy.convert(tick)); } ctx.stroke(); // Grid lines ctx.strokeStyle = 'rgb(189,189,189)'; ctx.setLineDash([5, 3]); ctx.beginPath(); for(let tick of y_ticks.slice(1).filter(x => x !== 0)) { ctx.moveTo(y_axes_x, canvas.height - cy.convert(tick)); ctx.lineTo(canvas.width, canvas.height - cy.convert(tick)); } for(let tick of x_ticks.slice(1).filter(x => x !== 0)) { ctx.moveTo(cx.convert(tick), x_axes_y); ctx.lineTo(cx.convert(tick), 0); } ctx.stroke(); ctx.setLineDash([]); ctx.strokeStyle = 'rgb(190,190,190)'; ctx.beginPath(); if (x_ticks.includes(0)) { ctx.moveTo(cx.convert(0), x_axes_y); ctx.lineTo(cx.convert(0), 0); } if (y_ticks.includes(0)) { ctx.moveTo(y_axes_x, canvas.height - cy.convert(0)); ctx.lineTo(canvas.width, canvas.height - cy.convert(0)); } ctx.stroke(); ctx.strokeStyle = 'rgb(253,30,30)'; ctx.lineWidth = 2; ctx.setLineDash([]); // Curve ctx.beginPath(); let prevNaN = true; for(let i = 0; i < X.length - 1; i++) { let isNaN = Number.isNaN(X[i]) || Number.isNaN(Y[i]); if(!isNaN && prevNaN) { ctx.moveTo(cx.convert(X[i]), canvas.height - cy.convert(Y[i])); } else if(!isNaN && !prevNaN) { ctx.lineTo(cx.convert(X[i]), canvas.height - cy.convert(Y[i])); } prevNaN = isNaN; } ctx.stroke(); } window_resize();
Editor is loading...