331 lines
9.9 KiB
JavaScript
331 lines
9.9 KiB
JavaScript
import {update_visible_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
|
|
* @param element_type path,g, etc
|
|
* @returns SVG element
|
|
*/
|
|
const create_svg_element = function (element_type) {
|
|
return document.createElementNS(SVG_NS, element_type);
|
|
}
|
|
|
|
/**
|
|
* removes child from element by id if found
|
|
* @param element
|
|
* @param child_id id to remove
|
|
*/
|
|
const remove_child = function (element, child_id) {
|
|
let node = element.firstChild;
|
|
while (node && child_id !== node.id) {
|
|
node = node.nextSibling;
|
|
}
|
|
if (node) {
|
|
element.removeChild(node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* calculate the screen coordinates for grid values
|
|
* @param x0 start_x
|
|
* @param y0 start_y
|
|
* @param x1 end_x
|
|
* @param y1 end y
|
|
* @returns {string} to put in an SVG path
|
|
*/
|
|
const calculate_d = function (x0, y0, x1, y1) {
|
|
return create_d(calc_screen_x(x0), calc_screen_y(y0), calc_screen_x(x1), calc_screen_y(y1));
|
|
}
|
|
|
|
const calc_screen_x = function (x) {
|
|
return origin_x + x * grid_size;
|
|
}
|
|
|
|
const calc_screen_y = function (y) {
|
|
return origin_y - y * grid_size;
|
|
}
|
|
/**
|
|
* create a d attribute from screen coordinates
|
|
* @param s_x0
|
|
* @param s_y0
|
|
* @param s_x1
|
|
* @param s_y1
|
|
* @returns {string}
|
|
*/
|
|
const create_d = function (s_x0, s_y0, s_x1, s_y1) {
|
|
return "M" + s_x0 + " " + s_y0 + " L" + s_x1 + " " + s_y1;
|
|
}
|
|
|
|
/**
|
|
* creates a SVG line (path)
|
|
* @param x0 start_x
|
|
* @param y0 start_y
|
|
* @param x1 end_x
|
|
* @param y1 end_y
|
|
* @param css_class the css class to make up the element
|
|
* @returns an SVG path element
|
|
*/
|
|
const create_line = function (x0, y0, x1, y1, css_class) {
|
|
let path = create_svg_element('path');
|
|
path.setAttribute('d', create_d(x0, y0, x1, y1));
|
|
path.setAttribute('class', css_class);
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* creates the arrow path element
|
|
* @param id attribute
|
|
* @param x0 start_x
|
|
* @param y0 start_y
|
|
* @param x1 end_x
|
|
* @param y1 end_y
|
|
* @param css_class class attribute
|
|
* @returns {SVGPathElement}
|
|
*/
|
|
const create_arrow = function (id, x0, y0, x1, y1, css_class) {
|
|
let path = create_svg_element('path');
|
|
|
|
path.setAttribute('d', calculate_d(x0, y0, x1, y1));
|
|
path.id = id;
|
|
path.setAttribute('class', css_class);
|
|
path.setAttribute('marker-end', 'url(#arrow)');
|
|
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
|
|
* @param css_class class for the lines that are 'multiples of the basis vector'
|
|
* @param bg_css_class class for in between lines
|
|
* @returns {SVGGElement}
|
|
*/
|
|
export const create_grid = function (css_class, bg_css_class) {
|
|
const group = create_svg_element('g');
|
|
group.setAttribute('id', 'grid');
|
|
const horizontal = create_svg_element('g');
|
|
horizontal.setAttribute('id', 'horizontal');
|
|
for (let y = 0; y < height; y += grid_size) {
|
|
horizontal.appendChild(create_line(0, y + half_grid_size, width, y + half_grid_size, css_class));
|
|
horizontal.appendChild(create_line(0, y, width, y, bg_css_class));
|
|
}
|
|
group.appendChild(horizontal);
|
|
const vertical = create_svg_element('g');
|
|
vertical.setAttribute('id', 'vertical');
|
|
for (let x = 0; x < width; x += grid_size) {
|
|
vertical.appendChild(create_line(x + half_grid_size, 0, x + half_grid_size, height, css_class));
|
|
vertical.appendChild(create_line(x, 0, x, height, bg_css_class));
|
|
}
|
|
group.appendChild(vertical);
|
|
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) {
|
|
let vector_group = get_or_create_vector_group();
|
|
let vector_arrow = create_arrow(vector.id, vector.x0, vector.y0, vector.x, vector.y, 'vector');
|
|
vector_arrow.onmousedown = function start_moving_vector(event) {
|
|
moving_vector = event.target;
|
|
};
|
|
vector_group.appendChild(vector_arrow);
|
|
|
|
let label = create_label(vector);
|
|
vector_group.appendChild(label);
|
|
}
|
|
|
|
/**
|
|
* Draws all the vectors.
|
|
*
|
|
* vector {
|
|
* x0,y0 origin
|
|
* x,y coordinates
|
|
* }
|
|
*/
|
|
const draw_vectors = function () {
|
|
const vector_group = get_or_create_vector_group();
|
|
|
|
let vectors = Object.values(vectors_by_id);
|
|
for (let i = 0; i < vectors.length; i++) {
|
|
if (vectors[i].visible) {
|
|
add_vector_arrow_to_svg(vectors[i]);
|
|
}
|
|
}
|
|
svg.appendChild(vector_group);
|
|
}
|
|
|
|
const get_or_create_vector_group = function () {
|
|
let vector_group = document.getElementById('vectors');
|
|
if (vector_group === null || vector_group === undefined) {
|
|
vector_group = create_svg_element("g");
|
|
svg.appendChild(vector_group);
|
|
vector_group.id = 'vectors';
|
|
}
|
|
|
|
return vector_group;
|
|
}
|
|
|
|
/**
|
|
* Removes all vectors in the svg and calls draw_vectors to draw updated versions.
|
|
*/
|
|
const redraw_vectors = function () {
|
|
remove_child(svg, 'vectors');
|
|
draw_vectors();
|
|
}
|
|
/**
|
|
* removes the grid from the DOM and adds an updated one.
|
|
*/
|
|
const redraw_grid = function () {
|
|
remove_child(svg, "grid");
|
|
svg.appendChild(create_grid('grid', 'bg-grid'));
|
|
remove_child(svg, 'axes');
|
|
svg.appendChild(create_axes());
|
|
}
|
|
|
|
export const update_vector_arrow = function (existing_id, new_vector) {
|
|
let d = calculate_d(new_vector.x0, new_vector.y0, new_vector.x, new_vector.y);
|
|
let arrow = document.getElementById(existing_id.toString());
|
|
if (arrow) {
|
|
arrow.setAttribute('d', d);
|
|
arrow.id = new_vector.id;
|
|
update_label(existing_id, new_vector.id, calc_screen_x(new_vector.x) + 5, calc_screen_y(new_vector.y) + 5);
|
|
} else {
|
|
add_vector_arrow_to_svg(new_vector);
|
|
}
|
|
delete vectors_by_id[existing_id];
|
|
vectors_by_id[new_vector.id] = new_vector;
|
|
}
|
|
|
|
/**
|
|
* (re)draws all
|
|
*/
|
|
export const redraw = function () {
|
|
redraw_grid();
|
|
redraw_vectors();
|
|
}
|
|
|
|
const create_axes = function () {
|
|
let axes_group = create_svg_element('g');
|
|
axes_group.setAttribute('id', 'axes');
|
|
let x = create_line(0, origin_y, width, origin_y, 'axis');
|
|
x.id = 'x-axis';
|
|
axes_group.appendChild(x);
|
|
let y = create_line(origin_x, 0, origin_x, height, 'axis');
|
|
y.id = 'y-axis';
|
|
axes_group.appendChild(y);
|
|
return axes_group;
|
|
}
|
|
|
|
/**
|
|
* setup the arrow head for the vector
|
|
* @returns {SVGDefsElement}
|
|
*/
|
|
const create_defs = function () {
|
|
let defs = create_svg_element('defs');
|
|
let marker = create_svg_element('marker');
|
|
marker.id = 'arrow';
|
|
marker.setAttribute('orient', 'auto');
|
|
marker.setAttribute('viewBox', '0 0 10 10');
|
|
marker.setAttribute('markerWidth', '3');
|
|
marker.setAttribute('markerHeight', '4');
|
|
marker.setAttribute('markerUnits', 'strokeWidth');
|
|
marker.setAttribute('refX', '6');
|
|
marker.setAttribute('refY', '5');
|
|
let polyline = create_svg_element('polyline');
|
|
polyline.setAttribute('points', '0,0 10,5 0,10 1,5');
|
|
polyline.setAttribute('fill', 'yellow');
|
|
marker.appendChild(polyline);
|
|
defs.appendChild(marker);
|
|
return defs;
|
|
}
|
|
|
|
/**
|
|
* The moving operation. Called by onmousemove on the svg ('canvas')
|
|
* @param event
|
|
*/
|
|
const move_vector = function (event) {
|
|
if (moving_vector) {
|
|
let current_x = event.clientX;
|
|
let current_y = event.clientY;
|
|
let vector = vectors_by_id[parseInt(moving_vector.id)];
|
|
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));
|
|
update_label(moving_vector.id, moving_vector.id, current_x + 5, current_y + 5);
|
|
update_visible_objects();
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates the SVG
|
|
* @returns {SVGElement}
|
|
*/
|
|
const create_svg = function () {
|
|
let svg = create_svg_element('svg');
|
|
|
|
svg.onmousemove = move_vector;
|
|
svg.onmouseup = function stop_moving_vector() {
|
|
moving_vector = undefined;
|
|
};
|
|
|
|
let defs = create_defs();
|
|
svg.appendChild(defs);
|
|
return svg;
|
|
}
|
|
|
|
document.body.onresize = function recalculate_window_dimensions() {
|
|
width = window.innerWidth;
|
|
height = window.innerHeight;
|
|
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;
|
|
redraw();
|
|
}
|
|
|
|
const svg = create_svg();
|
|
document.body.appendChild(svg);
|
|
svg.appendChild(create_grid('grid', 'bg-grid'));
|
|
svg.appendChild(create_axes());
|
|
get_or_create_vector_group();
|