@teachinglab/omd
Version:
omd
899 lines (704 loc) • 24.1 kB
JavaScript
// ================ jsvgObject ================================= //
export class jsvgObject
{
constructor()
{
this.name = "";
this.xpos = 0;
this.scale = 1.0;
this.rotation = 0.0;
this.ypos = 0;
this.width = 0;
this.height = 0;
this.opacity = 1.0;
this.visible = true;
this.svgObject = null;
this.childList = [];
this.parent = null;
}
// set properties
setName( N )
{
this.name = N;
}
setPosition( x, y )
{
this.xpos = x;
this.ypos = y;
this.updateTransform();
}
setScale( s )
{
this.scale = s;
this.updateTransform();
}
setRotation( r )
{
this.rotation = r;
this.updateTransform();
}
updateTransform()
{
// Ensure xpos and ypos are valid numbers
const x = isNaN(this.xpos) ? 0 : this.xpos;
const y = isNaN(this.ypos) ? 0 : this.ypos;
var transformParams = " translate(" + x.toString() + "," + y.toString() + ") "
if ( this.scale != 1.0 )
transformParams += " scale(" + this.scale.toString() + ")";
if ( this.rotation != 0.0 )
transformParams += " rotate(" + this.rotation + ")";
this.svgObject.setAttribute("transform", transformParams );
}
setWidth( w )
{
this.width = w;
this.svgObject.setAttribute("width", this.width.toString() );
}
setHeight( h )
{
this.height = h;
this.svgObject.setAttribute("height", this.height.toString() );
}
setWidthAndHeight( w,h )
{
this.setWidth( w );
this.setHeight( h );
}
setFillColor( color )
{
this.svgObject.setAttribute( "fill", color );
}
setStrokeColor( color )
{
this.svgObject.setAttribute( "stroke", color );
}
setStrokeWidth( sW )
{
this.svgObject.setAttribute( "stroke-width", sW.toString() );
}
setOpacity( opacity )
{
this.opacity = opacity;
this.svgObject.style.opacity = opacity;
}
setDropShadow()
{
// this is a generic dropshadow
this.svgObject.style.filter = "drop-shadow(0px 3px 6px rgba(0, 0, 0, 0.4))";
}
show()
{
this.visible = true;
this.svgObject.style.display = "";
}
hide()
{
this.visible = false;
this.svgObject.style.display = "none";
}
// update
update()
{
if ( ! this.visible )
return;
for( var C of this.childList )
{
C.update();
}
}
// child stuff
addChild( child )
{
if ( this.svgObject && child.svgObject )
{
this.childList.push( child );
this.svgObject.appendChild( child.svgObject );
child.parent = this;
}
}
getChild( index )
{
return this.childList[index];
}
replaceChild( newChild, oldChild )
{
// replace the node
this.svgObject.replaceChild( newChild.svgObject, oldChild.svgObject );
// replace in childList
for ( var i=0; i<this.childList.length; i++ )
{
if ( this.childList[i] == oldChild )
{
this.childList[i] = newChild;
break;
}
}
oldChild.parent = null;
newChild.parent = this;
}
getNumberOfChildren()
{
return this.childList.length;
}
removeChild( C )
{
const index = this.childList.indexOf(C);
this.removeChildByIndex( index );
C.parent = null;
}
removeChildByIndex( index )
{
if (index >= 0)
{
var C = this.childList[index];
this.childList.splice(index, 1);
this.svgObject.removeChild( C.svgObject );
C.parent = null;
}
}
removeAllChildren()
{
while( this.svgObject.firstChild )
{
this.svgObject.removeChild( this.svgObject.lastChild );
}
this.childList = [];
}
sortChildren( sortFunction )
{
this.childList.sort( sortFunction );
// If the child is a reference to an existing node in the document, appendChild() moves it from its current position to the new positio
this.childList.forEach(child => this.svgObject.appendChild( child.svgObject ));
}
// set up events
setClickCallback( callback )
{
if ( this.svgObject )
{
var outerThis = this;
this.svgObject.onclick = function() { callback(outerThis) };
this.svgObject.style.cursor = "pointer";
}
}
enableDragging()
{
this.svgObject.style.cursor = "pointer";
// on mouse down
var outerThis = this;
this.svgObject.onmousedown = function(E)
{
// console.log("mouse down");
outerThis.lastX = E.clientX;
outerThis.lastY = E.clientY;
// register on mouse move function (unregister on mouseup)
window.onmousemove = function(E)
{
// console.log("mouse move");
var dX = E.clientX - outerThis.lastX; // this method works if there's no scaling or rotation
var dY = E.clientY - outerThis.lastY;
outerThis.lastX = E.clientX;
outerThis.lastY = E.clientY;
outerThis.setPosition( outerThis.xpos+dX, outerThis.ypos+dY );
}
// on mouse up
window.onmouseup = function()
{
// console.log("mouse up");
window.onmousemove = null;
}
}
}
}
// ================ jsvgContainer ================================= //
export class jsvgContainer extends jsvgObject
{
constructor()
{
super();
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "svg");
this.svgObject.setAttribute("x", "0");
this.svgObject.setAttribute("y", "0");
this.svgObject.setAttribute("width", "500");
this.svgObject.setAttribute("height", "500");
this.svgObject.setAttribute("backgroundColor", "blue");
}
setViewbox( w, h )
{
this.setWidth(w);
this.setHeight(h);
var viewBoxParams = "0 0 " + w.toString() + " " + h.toString();
this.svgObject.setAttribute("viewBox", viewBoxParams );
}
}
// ================ jsvgGroup ================================= //
export class jsvgGroup extends jsvgObject
{
constructor()
{
super();
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "g");
this.svgObject.setAttribute("x", "0");
this.svgObject.setAttribute("y", "0");
this.svgObject.setAttribute("width", "500");
this.svgObject.setAttribute("height", "500");
this.svgObject.setAttribute("viewBox", "0 0 500 500");
}
}
// ================ jsvgLine ================================= //
export class jsvgLine extends jsvgObject
{
constructor()
{
super();
this.x1 = 0;
this.y1 = 0;
this.x2 = 100;
this.y2 = 100;
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "line");
this.svgObject.setAttribute("x1", "0");
this.svgObject.setAttribute("y1", "0");
this.svgObject.setAttribute("x2", "100");
this.svgObject.setAttribute("y2", "100");
this.setStrokeColor("black");
this.setStrokeWidth( 1 );
this.setEndpointA( this.x1, this.y1 );
this.setEndpointB( this.x2, this.y2 );
}
setEndpointA( x, y )
{
this.x1 = x;
this.y1 = y;
this.svgObject.setAttribute("x1", x.toString() );
this.svgObject.setAttribute("y1", y.toString() );
}
setEndpointB( x, y )
{
this.x2 = x;
this.y2 = y;
this.svgObject.setAttribute("x2", x.toString() );
this.svgObject.setAttribute("y2", y.toString() );
}
setEndpoints( x1,y1, x2,y2 )
{
this.setEndpointA( x1,y1 );
this.setEndpointB( x2,y2 );
}
}
// ================ jsvgRect ================================= //
export class jsvgRect extends jsvgObject
{
constructor()
{
super();
this.cornerRadius = 0;
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "rect");
this.setWidthAndHeight( 100,100 );
this.setFillColor( "white" );
this.setStrokeColor( "black" );
this.setStrokeWidth( 0 );
}
setCornerRadius( r )
{
this.cornerRadius = r;
this.svgObject.setAttribute( "rx", r.toString() );
this.svgObject.setAttribute( "ry", r.toString() );
}
}
// ================ jsvgEllipse ================================= //
export class jsvgEllipse extends jsvgObject
{
constructor()
{
super();
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "ellipse");
this.setPosition( 0,0 );
this.setWidthAndHeight( 100,100 );
this.setFillColor( "white" );
this.setStrokeColor( "black" );
this.setStrokeWidth( 0 );
}
setWidth( w )
{
this.width = w;
this.svgObject.setAttribute("rx", (this.width/2).toString() );
}
setHeight( h )
{
this.height = h;
this.svgObject.setAttribute("ry", (this.height/2).toString() );
}
}
// ================ jsvgArc ================================= //
export class jsvgArc extends jsvgObject
{
constructor()
{
super();
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "path");
this.setFillColor("white");
this.setStrokeColor("black");
this.setStrokeWidth(0);
}
createPieSlice(radius, startAngle, endAngle)
{
// Convert polar coordinates to Cartesian for SVG arc
function polarToCartesian(radius, angleInDegrees)
{
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180;
return {
x: (radius * Math.cos(angleInRadians)),
y: (radius * Math.sin(angleInRadians))
};
}
const start = polarToCartesian(radius, endAngle);
const end = polarToCartesian(radius, startAngle);
const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
const d = [
`M ${0} ${0}`, // Move to center
`L ${start.x} ${start.y}`, // Draw line to start of arc
`A ${radius} ${radius} 0 ${largeArcFlag} 0 ${end.x} ${end.y}`, // Draw arc
`Z` // Close path to center
].join(" ");
this.svgObject.setAttribute("d", d);
}
}
// ================ jsvgPath ================================= //
export class jsvgPath extends jsvgObject
{
constructor()
{
super();
// Create the path element and add it to the SVG element
this.path = document.createElementNS("http://www.w3.org/2000/svg", "path");
this.path.setAttribute("fill", "none"); // Default fill is none
this.path.setAttribute("stroke", "black"); // Default stroke is black
this.path.setAttribute("stroke-width", "2"); // Default stroke width
// this.svgElement.appendChild(this.path);
this.svgObject = this.path;
// Initialize the points array
this.points = [];
// Update the path's 'd' attribute
this.updatePath();
}
addPoint(x, y)
{
if (typeof x !== "number" || typeof y !== "number")
{
throw new Error("Coordinates must be numbers.");
}
this.points.push({ x:x, y:y });
// must call updatePath to see new points
}
updatePath()
{
if (this.points.length === 0)
{
this.path.setAttribute("d", "");
return;
}
// Create the 'd' attribute for the path
const d = this.points
.map((point, index) => (index === 0 ? `M ${point.x},${point.y}` : `L ${point.x},${point.y}`))
.join(" ");
// Close the polygon if it has at least three points
const pathData = d;
this.path.setAttribute("d", pathData);
}
clearPoints()
{
this.points = [];
this.updatePath();
}
}
// ================ jsvgClipMask ================================= //
export class jsvgClipMask extends jsvgObject
{
constructor( width=300, height=300, cornerRadius=10 )
{
super();
// make a new svg group
const svgNamespace = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNamespace, "g");
this.svgObject = svg;
this.width = width;
this.height = height;
this.cornerRadius = cornerRadius;
this.maskID = "clipMaskID" + Math.random();
svg.setAttribute("width", this.width);
svg.setAttribute("height", this.height);
svg.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`);
// Create a clipPath to apply rounded corners
const clipPath = document.createElementNS(svgNamespace, "clipPath");
clipPath.setAttribute("id", this.maskID );
this.clipRect = document.createElementNS(svgNamespace, "rect");
this.clipRect.setAttribute("x", 0);
this.clipRect.setAttribute("y", 0);
this.clipRect.setAttribute("width", this.width);
this.clipRect.setAttribute("height", this.height);
this.clipRect.setAttribute("rx", this.cornerRadius); // Rounded corner radius
this.clipRect.setAttribute("ry", this.cornerRadius);
clipPath.appendChild(this.clipRect);
svg.appendChild(clipPath);
// we create an inner group that gets masked
// all children get added this group
this.clipGroup = new jsvgGroup();
this.clipGroup.svgObject.setAttribute("clip-path", `url(#${this.maskID})`);
super.addChild( this.clipGroup ); // use super to avoid recursion
}
setWidth( width )
{
this.width = width;
this.svgObject.setAttribute("width", this.width);
this.svgObject.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`);
this.clipRect.setAttribute("width", this.width);
}
setHeight( height )
{
this.height = height;
this.svgObject.setAttribute("height", this.height);
this.svgObject.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`);
this.clipRect.setAttribute("height", this.height);
}
// setWidthAndHeight( width, height )
// {
// this.width = width;
// this.height = height;
// this.svgObject.setAttribute("width", this.width);
// this.svgObject.setAttribute("height", this.height);
// this.svgObject.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`);
// this.clipRect.setAttribute("width", this.width);
// this.clipRect.setAttribute("height", this.height);
// }
setCornerRadius( cornerRadius )
{
this.cornerRadius = cornerRadius;
this.clipRect.setAttribute("rx", this.cornerRadius); // Rounded corner radius
this.clipRect.setAttribute("ry", this.cornerRadius);
}
addChild( obj )
{
// add to masked clip group
this.clipGroup.addChild( obj );
}
}
// ================ jsvgImage ================================= //
export class jsvgImage extends jsvgObject
{
constructor()
{
super();
this.preserveAspectRatio = true;
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "image");
this.setWidthAndHeight( 100,100 );
}
setImageURL( newURL )
{
this.svgObject.setAttribute("href", newURL );
this.imageURL = newURL;
}
setAspectRatio( svgAspectRatio )
{
// to fill the whole space use "xMidYMid slice"
this.svgObject.setAttribute("preserveAspectRatio", svgAspectRatio );
}
applyRoundedCorners( cornerRadius, svgAspectRatio = 0 )
{
// remove the original image
if ( this.svgObject )
this.svgObject.remove();
// make a new svg image
const svgNamespace = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNamespace, "g");
this.svgObject = svg;
svg.setAttribute("width", this.width);
svg.setAttribute("height", this.height);
svg.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`);
// Create a clipPath to apply rounded corners
const clipPath = document.createElementNS(svgNamespace, "clipPath");
clipPath.setAttribute("id", "rounded-corners");
const rect = document.createElementNS(svgNamespace, "rect");
rect.setAttribute("x", 0);
rect.setAttribute("y", 0);
rect.setAttribute("width", this.width);
rect.setAttribute("height", this.height);
rect.setAttribute("rx", cornerRadius); // Rounded corner radius
rect.setAttribute("ry", cornerRadius);
clipPath.appendChild(rect);
svg.appendChild(clipPath);
// Create the image element
const image = document.createElementNS(svgNamespace, "image");
image.setAttribute("href", this.imageURL );
image.setAttribute("width", this.width);
image.setAttribute("height", this.height);
image.setAttribute("clip-path", "url(#rounded-corners)");
if ( svgAspectRatio )
image.setAttribute("preserveAspectRatio", svgAspectRatio );
svg.appendChild(image);
}
}
// ================ jsvgTextLine ================================= //
export class jsvgTextLine extends jsvgObject
{
constructor()
{
super();
this.text = "";
const svgNS = "http://www.w3.org/2000/svg";
this.svgObject = document.createElementNS(svgNS, "text");
this.svgObject.textContent = "hello world";
this.svgObject.setAttribute("x", "0"); // x-coordinate of the rectangle
this.svgObject.setAttribute("y", "0"); // y-coordinate of the rectangle
this.svgObject.setAttribute("fill", "black"); // fill color of the rectangle
this.svgObject.style.fontFamily = "Arial, Helvetica, sans-serif;";
}
setText( text ) { this.text = text; this.svgObject.textContent = text; }
getText() { return this.text; }
setFontSize( S ) { this.svgObject.style.fontSize = S.toString() + 'px'; }
setFontFamily( F ) { this.svgObject.style.fontFamily = F; }
setFontColor( C ) { this.svgObject.style.fill = C; }
setTextAnchor( A )
{
// left, middle or right
this.svgObject.setAttribute("text-anchor", A ); // fill color of the rectangle
}
setAlignment( A )
{
this.alignment = A;
if ( A == 'left' )
this.setTextAnchor( 'start' );
if ( A == 'center' )
this.setTextAnchor( 'middle' );
if ( A == 'right' )
this.setTextAnchor( 'end' );
}
setFontWeight( W ) {
this.svgObject.style.fontWeight = W.toString();
}
}
// ================ jsvgTextBox (Div) ================================= //
export class jsvgTextBox extends jsvgGroup
{
constructor()
{
super();
this.text = "";
var ns = 'http://www.w3.org/2000/svg';
this.foreignObject = document.createElementNS( ns, 'foreignObject');
this.foreignObject.style.backgroundColor = 'none';
this.svgObject.appendChild( this.foreignObject );
this.div = document.createElement('div');
this.foreignObject.appendChild( this.div );
this.setWidthAndHeight( 200,100 );
}
setText( T ) { this.text = T; this.div.innerHTML = T; }
getText() { return this.text; }
setStyle( S ) // for style setting with CSS
{
this.div.className = S;
this.div.style.position = 'fixed'; // for safari
};
setFontFamily( F ) { this.div.style.fontFamily = F; }
setFontSize( S ) { this.div.style.fontSize = S.toString() + "px"; }
setLineHeight( H ) { this.div.style.lineHeight = H + 'px'; }
setFontWeight( W ) { this.div.style.fontWeight = W; }
setFontColor( C ) { this.div.style.color = C; }
setAlignment( A ) { this.div.style.textAlign = A; }
setVerticalCentering()
{
this.div.style.display = "flex";
this.div.style.flexFlow = "column"
this.div.style.justifyContent = "center";
this.div.style.alignItems = "center";
}
makeEditable()
{
this.div.style.contentEditable = 'true';
this.div.onclick = function() { console.log("here"); this.focus(); };
}
setBorderWidthAndColor( width, color ) { this.div.style.border = width + "px solid " + color; }
setWidth( W )
{
this.width = W;
if ( this.div )
this.div.style.width = W + 'px'
if ( this.foreignObject )
this.foreignObject.setAttribute('width', W );
}
setHeight( H )
{
this.height = H;
if ( this.div )
this.div.style.height = H + 'px'
if ( this.foreignObject )
this.foreignObject.setAttribute('height', H );
}
}
// ================ jsvgTextInput ================================= //
export class jsvgTextInput extends jsvgTextBox
{
constructor()
{
super();
this.callback = null;
// remove the generic div before replacing it
this.foreignObject.removeChild( this.div );
this.div = document.createElement('input');
this.div.contentEditable = true;
this.div.onkeyup = this.handleInput.bind(this);
this.foreignObject.appendChild( this.div );
this.setWidthAndHeight( 200,30 );
this.setPlaceholderText("placeholder");
}
setStyle( S ) // for style setting with CSS
{
this.div.className = S;
};
setPlaceholderText( T ) { this.div.placeholder = T }
setText( T ) { this.div.value = T; }
getText() { return this.div.value; }
setInputCallback( callback ) { this.callback = callback; } // the callback is a function which receives text
handleInput( event )
{
if ( event.key == 'Enter' || event.keyCode==13 )
{
if ( this.callback )
this.callback( this.div.value )
}
}
}
// ================ jsvgTextArea ================================= //
export class jsvgTextArea extends jsvgTextBox
{
constructor()
{
super();
this.callback = null;
// remove the generic div before replacing it
this.foreignObject.removeChild( this.div );
this.div = document.createElement('textarea');
this.div.onkeyup = this.handleInput.bind(this);
this.foreignObject.appendChild( this.div );
this.setWidthAndHeight( 200,30 );
this.setPlaceholderText("placeholder");
}
setText( T ) { this.div.value = T; }
getText() { return this.div.value; }
setStyle( S ) // for style setting with CSS
{
this.div.className = S;
};
handleInput( event )
{
if ( this.callback )
this.callback( this );
}
setPlaceholderText( T ) { this.div.placeholder = T }
setInputCallback( callback ) { this.callback = callback; } // the callback is a function which receives text
}