fixed bugs and added functions
This commit is contained in:
parent
ea36a7c5a3
commit
26d4749995
4 changed files with 235 additions and 151 deletions
229
src/js/index.js
229
src/js/index.js
|
|
@ -1,57 +1,49 @@
|
||||||
import {scan, token_types} from './scanner';
|
import {scan, token_types} from './scanner';
|
||||||
import {parse} from './parser';
|
import {parse} from './parser';
|
||||||
import {add_vector_arrow_to_svg, remove_child, update_vector_arrow} from "./svg_functions";
|
import {
|
||||||
|
add_vector_arrow,
|
||||||
|
add_vector_arrow_to_svg,
|
||||||
|
remove_vector_arrow,
|
||||||
|
update_label_text,
|
||||||
|
update_vector_arrow
|
||||||
|
} from "./svg_functions";
|
||||||
|
|
||||||
export let vectors = []; // collection of added vectors // maybe move to console.js
|
const state = {}; // binding -> value
|
||||||
const state = {};
|
const bindings = {}; // binding -> {name:binding_name, evaluated: evaluated_lazy_value, previous: previous_value}
|
||||||
|
const references = {};
|
||||||
const command_input_element = document.getElementById('command_input');
|
const command_input_element = document.getElementById('command_input');
|
||||||
const command_history_element = document.getElementById('command_history');
|
const command_history_element = document.getElementById('command_history');
|
||||||
command_input_element.value = '';
|
command_input_element.value = '';
|
||||||
let command_history = [''];
|
let command_history = [''];
|
||||||
let command_history_index = 0;
|
let command_history_index = 0;
|
||||||
let vectors_index_sequence = 0;
|
let index_sequence = 0;
|
||||||
|
|
||||||
export const remove_vector = function (vector_or_index) {
|
const hide = function (vector) {
|
||||||
let index;
|
remove_vector_arrow(vector);
|
||||||
if (vector_or_index.is_vector) {
|
return {description: `vector@${vector.id} is hidden`};
|
||||||
for (let i = 0; i < vectors.length; i++) {
|
|
||||||
if (vectors[i].id === vector_or_index.id) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
index = vector_or_index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vectors[index]) {
|
const label = function (vector, text) {
|
||||||
throw {message: `vector@${index} not found`};
|
vector.label_text = text;
|
||||||
|
update_label_text(vector.id, text);
|
||||||
|
return `label set to ${text} on vector@${vector.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
vectors.splice(index, 1);
|
const show = function (vector) {
|
||||||
remove_child(document.getElementById('vectors'), index.toString());
|
add_vector_arrow_to_svg(vector);
|
||||||
return {description: `vector@${index} removed`};
|
return {description: `vector@${vector.id} is visible`};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const update_lazy_objects = function () {
|
export const update_lazy_objects = function () {
|
||||||
let lazy_objects = Object.values(state).filter(e => Object.prototype.hasOwnProperty.apply(e, ['lazy_expression']));
|
Object.values(bindings).forEach(binding => {
|
||||||
lazy_objects.forEach(object => {
|
if (state[binding.name].lazy_expression) {
|
||||||
let value = visit_expression(object.lazy_expression);
|
let value = visit(state[binding.name].lazy_expression);
|
||||||
let existing_value = state[object.binding];
|
let existing_value = bindings[binding.name].evaluated;
|
||||||
if (existing_value) {
|
if (existing_value) {
|
||||||
update_vector_arrow(existing_value.id, value);
|
update_vector_arrow(existing_value.id, value);
|
||||||
|
bindings[binding.name].evaluated = value;
|
||||||
}
|
}
|
||||||
state[object.binding].x0 = value.x0;
|
|
||||||
state[object.binding].y0 = value.y0;
|
|
||||||
state[object.binding].x = value.x;
|
|
||||||
state[object.binding].y = value.y;
|
|
||||||
state[object.binding].id = value.id;
|
|
||||||
let description = state[object.binding].description;
|
|
||||||
if (!description) {
|
|
||||||
description = state[object.binding];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {description: object.binding + ':' + description};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,17 +95,27 @@ const handle_enter = function () {
|
||||||
let statement = parse(tokens);
|
let statement = parse(tokens);
|
||||||
let value;
|
let value;
|
||||||
try {
|
try {
|
||||||
value = visit_expression(statement);
|
value = visit(statement);
|
||||||
|
let binding;
|
||||||
|
if (value.is_binding) { // if it's declaration work with the initializer
|
||||||
|
binding = value.name; // but we also need the name of the bound variable
|
||||||
|
value = state[binding];
|
||||||
|
}
|
||||||
|
while (value.lazy_expression) {
|
||||||
|
value = value.get();
|
||||||
|
}
|
||||||
|
if (binding) {
|
||||||
|
bindings[binding].evaluated = value; // store evaluation result
|
||||||
|
}
|
||||||
if (value.is_visual) {
|
if (value.is_visual) {
|
||||||
value.label = value.binding;
|
|
||||||
if (value.is_vector) {
|
if (value.is_vector) {
|
||||||
if (value.previous && value.previous.is_visual) {
|
if (binding && bindings[binding].previous && bindings[binding].previous.is_visual) {
|
||||||
update_vector_arrow(value.previous.id, value);
|
update_vector_arrow(value.previous.id, value);
|
||||||
} else {
|
} else {
|
||||||
if (value.is_new) {
|
if (value.is_new) {
|
||||||
|
value.label_text = binding ? binding : "";
|
||||||
value.is_new = false;
|
value.is_new = false;
|
||||||
vectors.push(value);
|
add_vector_arrow(value);
|
||||||
add_vector_arrow_to_svg(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,43 +126,36 @@ const handle_enter = function () {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
value = e.message;
|
value = e.message;
|
||||||
}
|
}
|
||||||
command_history_element.innerText += value + "\n";
|
command_history_element.innerText += value.toString() + "\n";
|
||||||
command_history.push(command);
|
command_history.push(command);
|
||||||
command_history_element.scrollTo(0, command_history_element.scrollHeight);
|
command_history_element.scrollTo(0, command_history_element.scrollHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const visit_expression = function (expr) {
|
const visit = function (expr) {
|
||||||
switch (expr.type) {
|
switch (expr.type) {
|
||||||
case 'declaration': {
|
case 'declaration': {
|
||||||
let value = visit_expression(expr.initializer);
|
let value = visit(expr.initializer);
|
||||||
if (!value.is_visual) { // if it's a primitive value,
|
let binding_name = expr.var_name.value;
|
||||||
value = { // wrap it into a object that returns the value
|
if (bindings[binding_name]) { // do reassignment
|
||||||
_value: value, // references the original value
|
bindings[binding_name].previous = state[binding_name]; // remember previous value, to remove it from the visualisation
|
||||||
toString: function () {
|
} else {
|
||||||
return this._value;
|
bindings[binding_name] = {
|
||||||
},
|
is_binding: true,
|
||||||
get: function () {
|
name: binding_name,
|
||||||
return this._value;
|
previous: null,
|
||||||
},
|
evaluated: null
|
||||||
is_visual: false
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
value.binding = expr.var_name.value; // store the variable name with it, to handle reassignment.
|
state[binding_name] = value; // assign new value to binding
|
||||||
if (state[value.binding]) { // do reassignment
|
|
||||||
value.previous = state[value.binding]; // remember previous value, to remove it from the visualisation
|
|
||||||
}
|
|
||||||
state[value.binding] = value; // assign new value to binding
|
|
||||||
|
|
||||||
update_lazy_objects(); // reevaluate any lazy expressions
|
return bindings[binding_name];
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
case 'group': // expression within parentheses
|
case 'group': // expression within parentheses
|
||||||
return visit_expression(expr.expression);
|
return visit(expr.expression);
|
||||||
case 'unary': {
|
case 'unary': {
|
||||||
let right_operand = visit_expression(expr.right);
|
let right_operand = visit(expr.right);
|
||||||
if (expr.operator === token_types.MINUS) {
|
if (expr.operator === token_types.MINUS) {
|
||||||
return -right_operand; //TODO create negate function (because now it only works for numbers)
|
return -right_operand; //TODO create negate function (because now it only works for numbers)
|
||||||
} else if (expr.operator === token_types.NOT) {
|
} else if (expr.operator === token_types.NOT) {
|
||||||
|
|
@ -170,50 +165,63 @@ const visit_expression = function (expr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'binary': {
|
case 'binary': {
|
||||||
let left = visit_expression(expr.left);
|
|
||||||
let right = visit_expression(expr.right);
|
|
||||||
switch (expr.operator) {
|
switch (expr.operator) {
|
||||||
case token_types.MINUS:
|
case token_types.MINUS:
|
||||||
return subtract(left, right);
|
return subtract(visit(expr.left), visit(expr.right));
|
||||||
case token_types.PLUS:
|
case token_types.PLUS:
|
||||||
return addition(left, right);
|
return addition(visit(expr.left), visit(expr.right));
|
||||||
case token_types.STAR:
|
case token_types.STAR:
|
||||||
return multiplication(left, right);
|
return multiplication(visit(expr.left), visit(expr.right));
|
||||||
case token_types.SLASH:
|
case token_types.SLASH:
|
||||||
return left / right;
|
return visit(expr.left) / visit(expr.right);
|
||||||
case token_types.DOT:
|
case token_types.DOT:
|
||||||
return method_call(left, expr.right);
|
return method_call(visit(expr.left), expr.right); // right is not evaluated. It's the method name
|
||||||
|
// could also be evaluated to itself, BUT it's of type call which would invoke a function (see below)
|
||||||
}
|
}
|
||||||
throw {message: 'illegal binary operator'};
|
throw {message: 'illegal binary operator'};
|
||||||
}
|
}
|
||||||
case 'identifier': {
|
case 'identifier': {
|
||||||
if (state[expr.name]) {
|
if (state[expr.name]) {
|
||||||
let object = state[expr.name];
|
let value = state[expr.name];
|
||||||
let get = object.get();
|
while (value.lazy_expression) {
|
||||||
return get;
|
value = value.get();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'literal':
|
case 'literal': {
|
||||||
return expr.value;
|
let value = expr.value;
|
||||||
|
while (value.lazy_expression) {
|
||||||
|
value = value.get();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
case 'call':
|
case 'call':
|
||||||
return function_call(expr.name, expr.arguments);
|
return function_call(expr.name, expr.arguments);
|
||||||
case 'lazy': {
|
case 'lazy': {
|
||||||
let r = visit_expression(expr.value);
|
let expression = expr.expression;
|
||||||
r.lazy_expression = expr.value;
|
let parsed_expression = expr.value;
|
||||||
return r;
|
return {
|
||||||
|
lazy_expression: parsed_expression,
|
||||||
|
toString: function () {
|
||||||
|
return `"${expression}"`;
|
||||||
|
},
|
||||||
|
get: function () {
|
||||||
|
return visit(parsed_expression);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'reference': {
|
||||||
|
return references[expr.name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const function_call = function (function_name, argument_exprs) {
|
const function_call = function (function_name, argument_exprs) {
|
||||||
let arguments_list = [];
|
|
||||||
for (let i = 0; i < argument_exprs.length; i++) {
|
|
||||||
arguments_list.push(visit_expression(argument_exprs[i]));
|
|
||||||
}
|
|
||||||
if (Object.prototype.hasOwnProperty.apply(functions, [function_name])) {
|
if (Object.prototype.hasOwnProperty.apply(functions, [function_name])) {
|
||||||
return functions[function_name](arguments_list);
|
return functions[function_name](resolve_arguments(argument_exprs));
|
||||||
} else {
|
} else {
|
||||||
let arg_list = '';
|
let arg_list = '';
|
||||||
for (let i = 0; i < argument_exprs.length; i++) {
|
for (let i = 0; i < argument_exprs.length; i++) {
|
||||||
|
|
@ -226,23 +234,23 @@ const function_call = function (function_name, argument_exprs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const method_call = function (object_wrapper, method_or_property) {
|
const method_call = function (object, method_or_property) {
|
||||||
if (object_wrapper) {
|
if (object) {
|
||||||
if (method_or_property.type === 'call') { // method
|
if (method_or_property.type === 'call') { // method
|
||||||
if (typeof object_wrapper[method_or_property.name] !== 'function') {
|
if (typeof object[method_or_property.name] !== 'function') {
|
||||||
throw {message: `method ${method_or_property.name} not found on ${object_wrapper.type}`};
|
throw {message: `method '${method_or_property.name}' not found on ${object.type}`};
|
||||||
}
|
}
|
||||||
|
|
||||||
return object_wrapper[method_or_property.name].apply(object_wrapper, method_or_property.arguments);
|
return object[method_or_property.name].apply(object, resolve_arguments(method_or_property.arguments));
|
||||||
|
|
||||||
} else { // property
|
} else { // property
|
||||||
if (!Object.prototype.hasOwnProperty.call(object_wrapper, [method_or_property.name])) {
|
if (!Object.prototype.hasOwnProperty.call(object, [method_or_property.name])) {
|
||||||
throw {message: `property ${method_or_property.name} not found on ${object_wrapper.type}`};
|
throw {message: `property '${method_or_property.name}' not found on ${object.type}`};
|
||||||
}
|
}
|
||||||
return object_wrapper[method_or_property.name];
|
return object[method_or_property.name];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw {message: `not found: ${object_wrapper}`};
|
throw {message: `not found: ${object}`};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,12 +263,15 @@ const functions = {
|
||||||
return create_vector({x0: args[0], y0: args[1], x: args[2], y: args[3]});
|
return create_vector({x0: args[0], y0: args[1], x: args[2], y: args[3]});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
remove: (args) => {
|
hide: (args) => {
|
||||||
if (Object.prototype.hasOwnProperty.call(args[0], ['binding'])) {
|
return hide(args[0]);
|
||||||
delete state[args[0].binding];
|
|
||||||
}
|
|
||||||
return remove_vector(args[0]);
|
|
||||||
},
|
},
|
||||||
|
label: (args) =>{
|
||||||
|
return label(args[0], args[1]);
|
||||||
|
},
|
||||||
|
show: (args) =>{
|
||||||
|
return show(args[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const help = function () {
|
const help = function () {
|
||||||
|
|
@ -317,17 +328,31 @@ const subtract = function (left, right) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const create_vector = function (vector) { //rename to create_vector
|
export const create_vector = function (vector) { //rename to create_vector
|
||||||
vector.id = vectors_index_sequence++;
|
vector.id = index_sequence++;
|
||||||
vector.is_visual = true;
|
vector.is_visual = true;
|
||||||
vector.is_vector = true;
|
vector.is_vector = true; // for comparison
|
||||||
|
vector.type = 'vector'; // for showing type to user
|
||||||
vector.is_new = true;
|
vector.is_new = true;
|
||||||
vector.toString = function () {
|
vector.toString = function () {
|
||||||
return `vector@${this.id}{x0:${vector.x0},y0:${vector.y0} x:${vector.x},y:${vector.y}}`;
|
return `vector@${this.id}{x0:${vector.x0},y0:${vector.y0} x:${vector.x},y:${vector.y}}`;
|
||||||
};
|
};
|
||||||
vector.get = function () {
|
references["@" + vector.id] = vector;
|
||||||
return vector;
|
vector.hide = function () {
|
||||||
}
|
return hide(this);
|
||||||
|
};
|
||||||
|
vector.label = function (text) {
|
||||||
|
return label(this, text);
|
||||||
|
};
|
||||||
|
vector.show = function () {
|
||||||
|
return show(this);
|
||||||
|
};
|
||||||
return vector;
|
return vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolve_arguments = function(argument_exprs) {
|
||||||
|
let arguments_list = [];
|
||||||
|
for (let i = 0; i < argument_exprs.length; i++) {
|
||||||
|
arguments_list.push(visit(argument_exprs[i]));
|
||||||
|
}
|
||||||
|
return arguments_list;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {token_types} from './scanner';
|
import {scan, token_types} from './scanner';
|
||||||
import {scan} from './scanner';
|
|
||||||
|
|
||||||
export const parse = function (tokens) {
|
export const parse = function (tokens) {
|
||||||
let token_index = 0;
|
let token_index = 0;
|
||||||
|
|
@ -112,20 +111,38 @@ export const parse = function (tokens) {
|
||||||
if (match([token_types.NUMERIC, token_types.STRING])) {
|
if (match([token_types.NUMERIC, token_types.STRING])) {
|
||||||
return {type: 'literal', value: previous_token().value, value_type: previous_token().type};
|
return {type: 'literal', value: previous_token().value, value_type: previous_token().type};
|
||||||
} else if (match([token_types.LAZY])) {
|
} else if (match([token_types.LAZY])) {
|
||||||
let tokens = scan(previous_token().expression);
|
let expression = previous_token().expression;
|
||||||
let expression = parse(tokens);
|
let tokens = scan(expression);
|
||||||
return {type: 'lazy', value: expression};
|
let parsed_expression = parse(tokens);
|
||||||
|
return {
|
||||||
|
type: 'lazy',
|
||||||
|
expression: expression,
|
||||||
|
value: parsed_expression
|
||||||
|
};
|
||||||
} else if (match([token_types.LEFT_PAREN])) {
|
} else if (match([token_types.LEFT_PAREN])) {
|
||||||
let expr = expression();
|
let expr = expression();
|
||||||
if (expr && match([token_types.RIGHT_PAREN])) {
|
if (expr && match([token_types.RIGHT_PAREN])) {
|
||||||
return {type: 'group', expression: expr};
|
return {
|
||||||
|
type: 'group',
|
||||||
|
expression: expr
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
throw {message: 'expected expression or )'};
|
throw {message: 'expected expression or )'};
|
||||||
}
|
}
|
||||||
} else if (check(token_types.IDENTIFIER, token_index)) {
|
} else if (check(token_types.IDENTIFIER, token_index)) {
|
||||||
let identifier = {type: 'identifier', name: current_token().value};
|
let identifier = {
|
||||||
|
type: 'identifier',
|
||||||
|
name: current_token().value
|
||||||
|
};
|
||||||
advance();
|
advance();
|
||||||
return identifier;
|
return identifier;
|
||||||
|
} else if (check(token_types.AT, token_index)) {
|
||||||
|
let result = {
|
||||||
|
type: 'reference',
|
||||||
|
name: current_token().value
|
||||||
|
};
|
||||||
|
advance();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,11 @@ export const scan = function (command) {
|
||||||
return string();
|
return string();
|
||||||
case '"':
|
case '"':
|
||||||
return lazy_expression();
|
return lazy_expression();
|
||||||
|
case '@': {
|
||||||
|
let reference = Object.assign({}, token_types.AT);
|
||||||
|
reference.value = parse_reference();
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (is_digit(next_char)) {
|
if (is_digit(next_char)) {
|
||||||
let token = Object.assign({}, token_types.NUMERIC);
|
let token = Object.assign({}, token_types.NUMERIC);
|
||||||
|
|
@ -118,6 +123,13 @@ export const scan = function (command) {
|
||||||
return is_digit(char) || char === '.'; // no scientific notation for now
|
return is_digit(char) || char === '.'; // no scientific notation for now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parse_reference() {
|
||||||
|
while (current_char() === '@' || is_digit(current_char())) {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
return command.substring(word_start_index, current_index);
|
||||||
|
}
|
||||||
|
|
||||||
function parse_number() {
|
function parse_number() {
|
||||||
while (is_part_of_number(current_char())) {
|
while (is_part_of_number(current_char())) {
|
||||||
advance();
|
advance();
|
||||||
|
|
@ -192,5 +204,6 @@ export const token_types = {
|
||||||
NUMERIC: {type: 'number', value: undefined},
|
NUMERIC: {type: 'number', value: undefined},
|
||||||
IDENTIFIER: {type: 'identifier', value: undefined},
|
IDENTIFIER: {type: 'identifier', value: undefined},
|
||||||
STRING: {type: 'string', value: undefined},
|
STRING: {type: 'string', value: undefined},
|
||||||
LAZY: {type: 'lazy', expression: undefined, parsed_expression:undefined}
|
LAZY: {type: 'lazy', expression: undefined, parsed_expression: undefined},
|
||||||
|
AT: {type: 'reference', value: undefined}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
import {update_lazy_objects, vectors} from "./index";
|
import {update_lazy_objects} from "./index";
|
||||||
|
|
||||||
|
let vectors_by_id = {};
|
||||||
|
const SVG_NS = 'http://www.w3.org/2000/svg'; // program needs these to create svg elements
|
||||||
|
let grid_size = 100; // this is the nr of pixels for the basis vector (1,0) (0,1)
|
||||||
|
let half_grid_size = grid_size >> 1; // used to position the grid lines
|
||||||
|
|
||||||
|
let moving_vector; // user can move vector arrows. when moving, this refers to the arrow
|
||||||
|
let width = window.innerWidth, height = window.innerHeight;
|
||||||
|
let origin_x = Math.floor((width / grid_size) / 2) * grid_size + half_grid_size,
|
||||||
|
origin_y = Math.floor((height / grid_size) / 2) * grid_size + half_grid_size;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an svg element
|
* Creates an svg element
|
||||||
|
|
@ -15,7 +24,7 @@ const create_svg_element = function (element_type) {
|
||||||
* @param element
|
* @param element
|
||||||
* @param child_id id to remove
|
* @param child_id id to remove
|
||||||
*/
|
*/
|
||||||
export const remove_child = function (element, child_id) {
|
const remove_child = function (element, child_id) {
|
||||||
let node = element.firstChild;
|
let node = element.firstChild;
|
||||||
while (node && child_id !== node.id) {
|
while (node && child_id !== node.id) {
|
||||||
node = node.nextSibling;
|
node = node.nextSibling;
|
||||||
|
|
@ -92,6 +101,12 @@ const create_arrow = function (id, x0, y0, x1, y1, css_class) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const remove_vector_arrow = function (vector) {
|
||||||
|
delete vectors_by_id[vector.id];
|
||||||
|
remove_child(document.getElementById('vectors'), vector.id.toString());
|
||||||
|
remove_child(document.getElementById('vectors'), "l" + vector.id.toString()); //
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the background grid of the space
|
* Draws the background grid of the space
|
||||||
* @param css_class class for the lines that are 'multiples of the basis vector'
|
* @param css_class class for the lines that are 'multiples of the basis vector'
|
||||||
|
|
@ -118,6 +133,37 @@ export const create_grid = function (css_class, bg_css_class) {
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const add_vector_arrow = function (vector) {
|
||||||
|
vectors_by_id[vector.id] = vector;
|
||||||
|
add_vector_arrow_to_svg(vector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_label(vector) {
|
||||||
|
let label = create_svg_element('text');
|
||||||
|
label.setAttribute('x', (calc_screen_x(vector.x) + 5).toString());
|
||||||
|
label.setAttribute('y', (calc_screen_y(vector.y) + 5).toString());
|
||||||
|
label.setAttribute('fill', 'yellow');
|
||||||
|
label.setAttribute('id', 'l' + vector.id);
|
||||||
|
let text_node = document.createTextNode(vector.label_text);
|
||||||
|
label.appendChild(text_node);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
const update_label = function (id, new_id, x, y) {
|
||||||
|
let label = document.getElementById('l' + id);
|
||||||
|
label.setAttribute('x', x.toString());
|
||||||
|
label.setAttribute('y', y.toString());
|
||||||
|
label.id = 'l' + new_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const update_label_text = function (id, text) {
|
||||||
|
if (text) {
|
||||||
|
let label = document.getElementById('l' + id);
|
||||||
|
label.firstChild.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const add_vector_arrow_to_svg = function (vector) {
|
export const add_vector_arrow_to_svg = function (vector) {
|
||||||
let vector_group = get_or_create_vector_group();
|
let vector_group = get_or_create_vector_group();
|
||||||
let vector_arrow = create_arrow(vector.id, vector.x0, vector.y0, vector.x, vector.y, 'vector');
|
let vector_arrow = create_arrow(vector.id, vector.x0, vector.y0, vector.x, vector.y, 'vector');
|
||||||
|
|
@ -126,16 +172,10 @@ export const add_vector_arrow_to_svg = function (vector) {
|
||||||
};
|
};
|
||||||
|
|
||||||
vector_group.appendChild(vector_arrow);
|
vector_group.appendChild(vector_arrow);
|
||||||
let label = create_svg_element('text');
|
|
||||||
label.setAttribute('x', (calc_screen_x(vector.x) + 5).toString());
|
let label = create_label(vector);
|
||||||
label.setAttribute('y', (calc_screen_y(vector.y) + 5).toString());
|
|
||||||
label.setAttribute('fill', 'yellow');
|
|
||||||
label.setAttribute('id', 'l' + vector.id);
|
|
||||||
let text = document.createTextNode(vector.label);
|
|
||||||
label.appendChild(text);
|
|
||||||
|
|
||||||
vector_group.appendChild(label);
|
vector_group.appendChild(label);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -149,6 +189,7 @@ export const add_vector_arrow_to_svg = function (vector) {
|
||||||
const draw_vectors = function () {
|
const draw_vectors = function () {
|
||||||
const vector_group = get_or_create_vector_group();
|
const vector_group = get_or_create_vector_group();
|
||||||
|
|
||||||
|
let vectors = Object.values(vectors_by_id);
|
||||||
for (let i = 0; i < vectors.length; i++) {
|
for (let i = 0; i < vectors.length; i++) {
|
||||||
add_vector_arrow_to_svg(vectors[i]);
|
add_vector_arrow_to_svg(vectors[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -183,13 +224,13 @@ const redraw_grid = function () {
|
||||||
svg.appendChild(create_axes());
|
svg.appendChild(create_axes());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const update_vector_arrow = function (id, vector) {
|
export const update_vector_arrow = function (existing_id, new_vector) {
|
||||||
let d = calculate_d(vector.x0, vector.y0, vector.x, vector.y);
|
let d = calculate_d(new_vector.x0, new_vector.y0, new_vector.x, new_vector.y);
|
||||||
let arrow = document.getElementById(id.toString());
|
let arrow = document.getElementById(existing_id.toString());
|
||||||
if (arrow) {
|
if (arrow) {
|
||||||
arrow.setAttribute('d', d);
|
arrow.setAttribute('d', d);
|
||||||
arrow.id = vector.id;
|
arrow.id = new_vector.id;
|
||||||
update_label_position(id, vector.id, calc_screen_x(vector.x) + 5, calc_screen_y(vector.y) + 5);
|
update_label(existing_id, new_vector.id, calc_screen_x(new_vector.x) + 5, calc_screen_y(new_vector.y) + 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,13 +277,6 @@ const create_defs = function () {
|
||||||
return defs;
|
return defs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const update_label_position = function (id, new_id, x, y) {
|
|
||||||
let label = document.getElementById('l' + id);
|
|
||||||
label.setAttribute('x', x.toString());
|
|
||||||
label.setAttribute('y', y.toString());
|
|
||||||
label.id = 'l' + new_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The moving operation. Called by onmousemove on the svg ('canvas')
|
* The moving operation. Called by onmousemove on the svg ('canvas')
|
||||||
* @param event
|
* @param event
|
||||||
|
|
@ -251,13 +285,16 @@ const move_vector = function (event) {
|
||||||
if (moving_vector) {
|
if (moving_vector) {
|
||||||
let current_x = event.clientX;
|
let current_x = event.clientX;
|
||||||
let current_y = event.clientY;
|
let current_y = event.clientY;
|
||||||
vectors[moving_vector.id].x = (current_x - origin_x) / grid_size;
|
let vector = vectors_by_id[parseInt(moving_vector.id)];
|
||||||
vectors[moving_vector.id].y = (origin_y - current_y) / grid_size;
|
if (vector) {
|
||||||
|
vector.x = (current_x - origin_x) / grid_size;
|
||||||
|
vector.y = (origin_y - current_y) / grid_size;
|
||||||
moving_vector.setAttribute('d', create_d(origin_x, origin_y, current_x, current_y));
|
moving_vector.setAttribute('d', create_d(origin_x, origin_y, current_x, current_y));
|
||||||
update_label_position(moving_vector.id,moving_vector.id, current_x + 5, current_y + 5);
|
update_label(moving_vector.id, moving_vector.id, current_x + 5, current_y + 5);
|
||||||
update_lazy_objects();
|
update_lazy_objects();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Creates the SVG
|
* Creates the SVG
|
||||||
* @returns {SVGElement}
|
* @returns {SVGElement}
|
||||||
|
|
@ -283,14 +320,6 @@ document.body.onresize = function recalculate_window_dimensions() {
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
const SVG_NS = 'http://www.w3.org/2000/svg'; // program needs these to create svg elements
|
|
||||||
let grid_size = 100; // this is the nr of pixels for the basis vector (1,0) (0,1)
|
|
||||||
let half_grid_size = grid_size >> 1; // used to position the grid lines
|
|
||||||
|
|
||||||
let moving_vector; // user can move vector arrows. when moving, this refers to the arrow
|
|
||||||
let width = window.innerWidth, height = window.innerHeight;
|
|
||||||
let origin_x = Math.floor((width / grid_size) / 2) * grid_size + half_grid_size,
|
|
||||||
origin_y = Math.floor((height / grid_size) / 2) * grid_size + half_grid_size;
|
|
||||||
const svg = create_svg();
|
const svg = create_svg();
|
||||||
document.body.appendChild(svg);
|
document.body.appendChild(svg);
|
||||||
svg.appendChild(create_grid('grid', 'bg-grid'));
|
svg.appendChild(create_grid('grid', 'bg-grid'));
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue