rdom-svg-nodes

rdom powered SVG graph with draggable nodes github , github

The program starts by importing several functions from different modules via CDN. These functions include defAtom, equivArrayLike, circle, line, svg, $compile, $list, fromAtom, indexed, partition, repeatedly and random2.

import { defAtom } from 'https://esm.run/@thi.ng/atom'; import { equivArrayLike } from 'https://esm.run/@thi.ng/equiv'; import { circle, line, svg } from 'https://esm.run/@thi.ng/hiccup-svg'; import { $compile, $list } from 'https://esm.run/@thi.ng/rdom'; import { fromAtom } from 'https://esm.run/@thi.ng/rstream'; import { indexed, partition, repeatedly } from "https://esm.run/@thi.ng/transducers"; import { random2 } from "https://esm.run/@thi.ng/vectors";

The defAtom function creates a new reactive atom with an initial value of an array of NUM random points within a square area with a side length of WIDTH.

const WIDTH = 600; const NUM = 10; const R = 20; // define atom of NUM random points const db = defAtom([...repeatedly(() => random2([], R, WIDTH - R), NUM)]);

The program then defines a clicked variable to keep track of which point is currently being dragged (initialized to -1).

// ID of currently dragged point let clicked = -1;

//wiki.ralfbarkow.ch/assets/pages/js-snippet-template/esm.html HEIGHT 256

The $compile function creates an SVG element with width, height, and viewBox attributes to be displayed in the frame above, and mouse event handlers for drag-and-drop interaction.

$compile(svg({ width: WIDTH, height: WIDTH, viewBox: `0 0 ${WIDTH} ${WIDTH}`, // mouse drag & release handlers are on SVG element itself // for better UX when fast dragging onmousemove: (e) => clicked !== -1 && db.resetIn([clicked], [e.clientX, e.clientY]), onmouseup: () => (clicked = -1), // add new point on double click ondblclick: (e) => db.swap((pts) => [...pts, [e.clientX, e.clientY]]), },

The $list function is used to create two reactive lists, one for lines and one for circles, each with a corresponding set of attributes and mouse event handlers.

// reactive "list" of lines $list( // transform atom view into consecutive pairs: // e.g. [a,b,c,d] => [[a,b],[b,c],[c,d]] fromAtom(db).map((pts) => [...partition(2, 1, pts)]), // list wrapper element & its attribs "g", { stroke: "#00f" }, // list item constructor (here an SVG line) ([a, b]) => line(a, b), // value based equivalence predicate // applied to raw values from stream (here point pairs) // item ctors are only called if predicate returns false equivArrayLike), // reactive list of circles $list( // transform atom view to label each point with its array index // (needed to determine point selection in mouse event handler) fromAtom(db).map((pts) => [...indexed(0, pts)]), // list wrapper element & its attribs "g", { fill: "#00f" }, // list items here are SVG circles

To create the line and circle elements, the program uses a combination of the map and indexed functions to transform the array of points into consecutive pairs for lines, and to assign a unique index to each point for circles. The circle and line functions are used to create the corresponding SVG elements for each point pair.

When the user clicks on a circle element, it becomes selected, and the mousedown event handler updates the clicked variable with its index. When the user drags a selected circle element, the mousemove event handler updates its position in the reactive atom. When the user releases the mouse button, the mouseup event handler sets clicked back to -1.

// update selection & point position in atom on mouse click ([i, p]) => circle(p, R, { onmousedown: (e) => { clicked = i; db.resetIn([i], [e.clientX, e.clientY]); }, }), // value based equivalence predicate equivArrayLike))).mount(document.getElementById("output"));

Note: The id property has been changed from "app" to "output".

Overall, the program creates a simple interactive SVG graph with draggable nodes that can be used for various applications, such as data visualization or interactive art.