Published
Edited
Apr 10, 2020
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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,
};
}
}
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more