Untitled
unknown
javascript
4 years ago
7.8 kB
37
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...