const { dia, shapes, util } = joint;
const GRID_SIZE = 20;
const GRID_COUNT = 12;
// The value which ensures visibility of elements in the Z direction
const PAPER_Z_OFFSET = GRID_SIZE * 4;
// Matrix of the isometric transformation and its parameters
const SCALE = 1;
const ISOMETRIC_SCALE = 0.8602;
const ROTATION_DEGREES = 30;
const transformationMatrix = () => {
return V.createSVGMatrix()
.translate(GRID_COUNT * GRID_SIZE * SCALE * ISOMETRIC_SCALE + GRID_SIZE, PAPER_Z_OFFSET + GRID_SIZE)
.rotate(ROTATION_DEGREES)
.skewX(-ROTATION_DEGREES)
.scaleNonUniform(SCALE, SCALE * ISOMETRIC_SCALE);
};
// Here we are specifying element markup and
// constant properties of markup parts
const IsometricElement = dia.Element.define(
'IsometricElement',
{
attrs: {
top1: {
strokeWidth: 2,
stroke: '#333333',
fill: '#ff0000',
fillOpacity: 0.7,
},
top2: {
strokeWidth: 2,
stroke: '#333333',
fill: '#ff0000',
fillOpacity: 0.7,
},
side1: {
strokeWidth: 2,
stroke: '#333333',
fill: '#ffff00',
fillOpacity: 0.7,
},
side2: {
strokeWidth: 2,
stroke: '#333333',
fill: '#ffff00',
fillOpacity: 0.7
},
front1: {
strokeWidth: 2,
stroke: '#333333',
fill: '#0000ff',
fillOpacity: 0.7,
},
front2: {
strokeWidth: 2,
stroke: '#333333',
fill: '#0000ff',
fillOpacity: 0.7,
}
}
},
{
markup: util.svg/* xml */ `
<polygon @selector="top1"></polygon>
<polygon @selector="top2"></polygon>
<polygon @selector="side1"></polygon>
<polygon @selector="side2"></polygon>
<polygon @selector="front1"></polygon>
<polygon @selector="front2"></polygon>
`
}
);
// converting dimension parameters into path properties of markup parts
// so the element can be created in a more flexible way
const createIsometricElement = (properties) => {
const d = {
x: properties.size.width,
y: properties.size.height,
z: properties.isometricHeight
};
properties.attrs = properties.attrs || {};
properties.attrs.top1 = {
points: `0,0 ${d.x},0 ${d.x},${GRID_SIZE} ${d.x - GRID_SIZE},${GRID_SIZE} ${d.x - GRID_SIZE},${d.y} 0,${d.y}`,
transform: `translate(${-d.z}, ${-d.z})`
};
properties.attrs.top2 = {
points: `${d.x - GRID_SIZE},${GRID_SIZE} ${d.x},${GRID_SIZE} ${d.x},${d.y} ${d.x - GRID_SIZE},${d.y}`,
transform: `translate(${-d.z + GRID_SIZE * 2}, ${-d.z + GRID_SIZE * 2})`
};
properties.attrs.side1 = {
points: `0,0 ${d.x - GRID_SIZE},0 ${d.x - GRID_SIZE},${GRID_SIZE * 2} ${d.x},${GRID_SIZE * 2} ${d.x},${d.z} 0,${d.z}`,
transform: V.matrixToTransformString(
new DOMMatrixReadOnly()
.translate(-d.z, -d.z + d.y)
.skewX(45)
)
};
properties.attrs.side2 = {
points: `0,0 ${GRID_SIZE},0 ${GRID_SIZE},${GRID_SIZE * 2} 0,${GRID_SIZE * 2}`,
transform: V.matrixToTransformString(
new DOMMatrixReadOnly()
.translate(-d.z + d.x - GRID_SIZE, -d.z + GRID_SIZE)
.skewX(45)
)
};
properties.attrs.front1 = {
points: `0,0 ${d.z},0 ${d.z},${d.y} ${GRID_SIZE * 2},${d.y}, ${GRID_SIZE * 2},${GRID_SIZE} 0,${GRID_SIZE}`,
transform: V.matrixToTransformString(
new DOMMatrixReadOnly()
.translate(-d.z + d.x, -d.z)
.skewY(45)
)
},
properties.attrs.front2 = {
points: `0,0 ${GRID_SIZE * 2},0 ${GRID_SIZE * 2},${d.y - GRID_SIZE} 0,${d.y - GRID_SIZE}`,
transform: V.matrixToTransformString(
new DOMMatrixReadOnly()
.translate(-d.z + d.x - GRID_SIZE, -d.z + GRID_SIZE)
.skewY(45)
)
};
return new IsometricElement(properties);
};
// Paper
const cellNamespace = { ...shapes, IsometricElement };
const graph = new dia.Graph({}, { cellNamespace });
const paper = new dia.Paper({
el: document.getElementById('paper-container'),
model: graph,
restrictTranslate: {
x: 0,
y: 0,
width: GRID_SIZE * GRID_COUNT,
height: GRID_SIZE * GRID_COUNT
},
width: '100%',
height: '100%',
gridSize: GRID_SIZE,
async: true,
autoFreeze: true,
sorting: dia.Paper.sorting.APPROX,
cellViewNamespace: cellNamespace
});
// Make the paper isometric by applying the isometric matrix to all
// SVG content it contains.
paper.matrix(transformationMatrix());
// Add isometric element to the graph.
// You can specify dimensions of the element using element's size and additional z parameter
const element = createIsometricElement({
isometricHeight: GRID_SIZE * 4,
size: { width: GRID_SIZE * 3, height: GRID_SIZE * 6 },
position: { x: GRID_SIZE * 6, y: GRID_SIZE * 6 }
});
element.addTo(graph);
// A function to draw the grid.
drawGrid(paper);
function drawGrid(paper) {
const gridData = [];
const j = GRID_COUNT;
for (let i = 0; i <= j; i++) {
gridData.push(`M 0,${i * GRID_SIZE} ${j * GRID_SIZE},${i * GRID_SIZE}`);
gridData.push(`M ${i * GRID_SIZE},0 ${i * GRID_SIZE},${j * GRID_SIZE}`);
}
const gridEl = V('path').attr({
d: gridData.join(' '),
fill: 'none',
stroke: 'lightgray'
}).node;
// When the grid is appended to one of the paper's layer, it gets automatically transformed
// by the isometric matrix
paper.getLayerNode(dia.Paper.Layers.BACK).append(gridEl);
}
// Add switch to toggle the isometric view with 2d for demonstration purposes
document
.getElementById('isometric-switch')
.addEventListener('change', (evt) => {
if (evt.target.checked) {
paper.matrix(transformationMatrix());
} else {
paper.matrix(
V.createSVGMatrix().translate(
GRID_SIZE * GRID_COUNT,
PAPER_Z_OFFSET + GRID_SIZE
)
);
}
});