class Drawing {
constructor({width, height, layers=['default'], viewbox}) {
let container = html`<div></div>`;
let stage = new Konva.Stage({
container: container,
width: width,
height: height,
});
let layerMap = new Map();
for (let layerName of layers) {
let layer = new Konva.FastLayer();
stage.add(layer);
layerMap.set(layerName, layer);
}
let scaleX = viewbox.width / width;
let scaleY = viewbox.height / height;
let scale = Math.max(scaleX, scaleY);
this.view = new View({width, height, offsetX: viewbox.x, offsetZ: viewbox.z, scale});
this.width = width;
this.height = height;
this.stage = stage;
this.layerMap = layerMap;
this.currentLayer = layerMap.get(layers[0]);
}
container() {
this.stage.draw();
return this.stage.container();
}
clear() {
for (let layer of this.stage.getLayers()) {
layer.destroyChildren();
}
}
setCurrentLayer(name) {
this.currentLayer = this.layerMap.get(name);
}
showLayer(name) {
this.layerMap.get(name).show();
}
hideLayer(name) {
this.layerMap.get(name).hide();
}
line({a, b, color='black', strokeWidth=1, dash=null, hidden=false}) {
let aScreen = this.view.pointToScreen(a);
let bScreen = this.view.pointToScreen(b);
if (!hidden) {
this.currentLayer.add(new Konva.Line({
points: [aScreen.x, aScreen.y, bScreen.x, bScreen.y],
stroke: color,
strokeWidth: strokeWidth,
dash: dash,
lineCap: 'round',
lineJoin: 'round',
}));
}
}
text({p, text, rotation=0, color='black', offsetX=0, offsetY=0, hidden=false}) {
let pScreen = this.view.pointToScreen(p);
if (!hidden) {
this.currentLayer.add(new Konva.Text({
x: pScreen.x,
y: pScreen.y,
rotation,
text: text,
fontSize: 18,
fontFamily: 'sans-serif',
fill: color,
height: 0,
verticalAlign: 'middle',
offsetX,
offsetY,
}));
}
return {
location: p,
}
}
node({key, p, color='black', fill='white', label=true, textX=10, textY=10, support='', hidden=false}) {
let screen = this.view.pointToScreen(p);
if (!hidden) {
if (support.includes('x') && !support.includes('z')) {
this.currentLayer.add(new Konva.Line({
x: screen.x,
y: screen.y,
rotation: -90,
points: [0,0, -12,18, 12,18],
stroke: color,
strokeWidth: 1,
lineCap: 'round',
lineJoin: 'round',
closed: true,
}));
this.currentLayer.add(new Konva.Line({
x: screen.x,
y: screen.y,
rotation: -90,
points: [-12,23, 12,23],
stroke: color,
strokeWidth: 1,
lineCap: 'round',
lineJoin: 'round',
}));
} else if (!support.includes('x') && support.includes('z')) {
this.currentLayer.add(new Konva.Line({
x: screen.x,
y: screen.y,
points: [0,0, -12,18, 12,18],
stroke: color,
strokeWidth: 1,
lineCap: 'round',
lineJoin: 'round',
closed: true,
}));
this.currentLayer.add(new Konva.Line({
x: screen.x,
y: screen.y,
points: [-12,23, 12,23],
stroke: color,
strokeWidth: 1,
lineCap: 'round',
lineJoin: 'round',
}));
} else if (support.includes('x') || support.includes('z')) {
this.currentLayer.add(new Konva.Line({
x: screen.x,
y: screen.y,
points: [0,0, -12,18, 12,18],
stroke: color,
strokeWidth: 1,
lineCap: 'round',
lineJoin: 'round',
closed: true,
}));
}
if (label != false) {
this.currentLayer.add(new Konva.Text({
x: screen.x + textX,
y: screen.y + textY,
text: label === true ? key : label,
fontSize: 18,
fontFamily: 'sans-serif',
fill: color,
height: 0,
verticalAlign: 'middle',
scaleY: -1,
transformsEnabled: 'position',
}));
}
this.currentLayer.add(new Konva.Circle({
x: screen.x,
y: screen.y,
radius: 5,
stroke: color,
fill: fill,
strokeWidth: 1,
}));
}
return {
location: p,
};
}
truss({key, nodes, color='black', label=true, textX=0, textY=10, hidden=false}) {
let [a, b] = nodes;
let aScreen = this.view.pointToScreen(a);
let bScreen = this.view.pointToScreen(b);
if (!hidden) {
this.currentLayer.add(new Konva.Line({
points: [aScreen.x, aScreen.y, bScreen.x, bScreen.y],
stroke: color,
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round',
transformsEnabled: 'position',
}));
if (label != false) {
let angle = Math.atan2(bScreen.y - aScreen.y, bScreen.x - aScreen.x) * 180 / Math.PI;
this.currentLayer.add(new Konva.Text({
x: (aScreen.x + bScreen.x) / 2,
y: (aScreen.y + bScreen.y) / 2,
rotation: angle,
text: label === true ? key : label,
fontSize: 18,
fontFamily: 'sans-serif',
fill: color,
height: 0,
verticalAlign: 'middle',
offsetX: textX,
offsetY: textY,
}));
}
}
}
force({p, f, scale=1, color='black', strokeWidth=1, dash=null, epsilon=1e-4, hidden=false}) {
let pScreen = this.view.pointToScreen(p);
let fScreen = {
x: this.view.lengthToScreen(f.x) * scale,
y: -this.view.lengthToScreen(f.z) * scale,
};
if (!hidden && Math.sqrt(f.x * f.x + f.z * f.z) >= epsilon) {
this.currentLayer.add(new Konva.Arrow({
points: [pScreen.x, pScreen.y, pScreen.x + fScreen.x, pScreen.y + fScreen.y],
pointerLength: 16,
pointerWidth: 6,
fill: color,
stroke: color,
strokeWidth: strokeWidth,
dash: dash,
lineCap: 'round',
lineJoin: 'round',
transformsEnabled: 'position',
}));
}
let to = this.view.pointToModel({x: pScreen.x + fScreen.x, y: pScreen.y + fScreen.y});
return {
from: p,
to: to,
mid: {x: (p.x + to.x) / 2, z: (p.z + to.z) / 2},
force: f,
}
}
forceTo({p, f, scale=1, color='black', strokeWidth=1, dash=null, epsilon=1e-4, hidden=false}) {
let pScreen = this.view.pointToScreen(p);
let fScreen = {
x: this.view.lengthToScreen(f.x) * scale,
y: -this.view.lengthToScreen(f.z) * scale,
};
if (!hidden && Math.sqrt(f.x * f.x + f.z * f.z) >= epsilon) {
this.currentLayer.add(new Konva.Arrow({
points: [pScreen.x - fScreen.x, pScreen.y - fScreen.y, pScreen.x, pScreen.y],
pointerLength: 16,
pointerWidth: 6,
fill: color,
stroke: color,
strokeWidth: strokeWidth,
dash: dash,
lineCap: 'round',
lineJoin: 'round',
transformsEnabled: 'position',
}));
}
let from = this.view.pointToModel({x: pScreen.x - fScreen.x, y: pScreen.y - fScreen.y});
return {
from: from,
to: p,
mid: {x: (p.x + from.x) / 2, z: (p.z + from.z) / 2},
force: f,
}
}
force2p({a, b, amplitude, scale=1, color='black', strokeWidth=1, epsilon=1e-4, hidden=false}) {
let screen = this.view.pointToScreen(a);
let angle = Math.atan2(b.z - a.z, b.x - a.x);
let screenAmplitude = this.view.lengthToScreen(amplitude) * scale;
let sizeX = Math.cos(angle) * screenAmplitude;
let sizeY = Math.sin(angle) * screenAmplitude;
if (!hidden && Math.abs(amplitude) >= epsilon) {
this.currentLayer.add(new Konva.Arrow({
points: [screen.x, screen.y, screen.x + sizeX, screen.y - sizeY],
pointerLength: 16,
pointerWidth: 6,
fill: color,
stroke: color,
strokeWidth: strokeWidth,
lineCap: 'round',
lineJoin: 'round',
transformsEnabled: 'position',
}));
}
let to = this.view.pointToModel({x: screen.x + sizeX, y: screen.y - sizeY});
return {
from: a,
to: to,
mid: {x: (a.x + to.x) / 2, z: (a.z + to.z) / 2},
force: {x: Math.cos(angle) * amplitude, z: Math.sin(angle) * amplitude},
}
}
moment({p, radius=40, color='black', strokeWidth=1})
{
let sin = v => Math.sin(v * Math.PI / 180);
let cos = v => Math.cos(v * Math.PI / 180);
let pScreen = this.view.pointToScreen(p);
this.currentLayer.add(new Konva.Arc({
x: pScreen.x,
y: pScreen.y,
innerRadius: radius,
outerRadius: radius,
fill: null,
stroke: color,
strokeWidth: strokeWidth,
angle: 270,
rotationDeg: 45,
lineCap: 'round',
lineJoin: 'round',
}));
this.currentLayer.add(new Konva.Arrow({
x: pScreen.x,
y: pScreen.y,
points: [cos(70) * radius, sin(70) * radius, cos(45) * radius, sin(45) * radius],
pointerLength: 16,
pointerWidth: 6,
fill: color,
stroke: color,
strokeWidth: strokeWidth,
lineCap: 'round',
lineJoin: 'round',
transformsEnabled: 'position',
}));
return {
location: p,
};
}
spring({a, b, color='black', strokeWidth=1})
{
let sin = v => Math.sin(v * Math.PI / 180);
let cos = v => Math.cos(v * Math.PI / 180);
if (b == null) {
b = a;
}
let aScreen = this.view.pointToScreen(a);
let bScreen = this.view.pointToScreen(b);
let angle = Math.atan2(bScreen.x - aScreen.x, aScreen.y - bScreen.y) * 180 / Math.PI;
let length = Math.sqrt(Math.pow(bScreen.x - aScreen.x, 2) + Math.pow(bScreen.y - aScreen.y, 2));
let d = (24 + length) / 7
this.currentLayer.add(new Konva.Line({
x: bScreen.x,
y: bScreen.y,
rotation: angle,
points: [0,0, 0,10,
10,10+1*d, -10,10+2*d,
10,10+3*d, -10,10+4*d,
10,10+5*d, -10,10+6*d,
0,10+7*d, 0,20+7*d],
fill: null,
stroke: color,
strokeWidth: strokeWidth,
lineCap: 'round',
lineJoin: 'round',
}));
this.currentLayer.add(new Konva.Line({
x: bScreen.x,
y: bScreen.y,
rotation: angle,
points: [-12,20+7*d, 12,20+7*d],
fill: null,
stroke: color,
strokeWidth: strokeWidth,
lineCap: 'round',
lineJoin: 'round',
}));
return {
from: a,
to: b,
};
}
}