<div id="paper-container"></div>
<label>
  <span>Isometric Transformation:</span>
  <input type="checkbox" id="isometric-switch" checked />
</label>
<a target="_blank" href="https://www.jointjs.com">
  <img id="logo" src="https://assets.codepen.io/7589991/jj-logo-red.svg" width="200" height="50"></img>
</a>
#paper-container {
  position: absolute;
  right: 0;
  top: 0;
  left: 0;
  bottom: 0;
}

#logo {
  position: absolute;
  bottom: 20px;
  right: 0;
}

label {
  position: absolute;
  top: 30px;
  right: 30px;
  font-family: sans-serif;
}

label input {
 vertical-align: text-top;
}

const { dia, shapes, util } = joint;

const GRID_SIZE = 20;
const GRID_COUNT = 12;

// 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_SIZE * GRID_COUNT, GRID_SIZE)
    .rotate(ROTATION_DEGREES)
    .skewX(-ROTATION_DEGREES)
    .scaleNonUniform(SCALE, SCALE * ISOMETRIC_SCALE);
};

// Paper

const cellNamespace = { ...shapes };

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());

const gVEl = V("g", { fill: "#ed2637" });
const rectVEl = V("rect", {
  width: GRID_SIZE,
  height: GRID_SIZE,
  stroke: "#ed2637",
  "stroke-width": 1
});
const textVEl = V("text").attr({
  "text-anchor": "start",
  x: 2 * GRID_SIZE,
  "font-size": GRID_SIZE,
  "font-family": "sans-serif",
  stroke: "white",
  "stroke-width": 3,
  "paint-order": "stroke"
});
gVEl.append([rectVEl, textVEl]);

paper.el.addEventListener(
  "mousemove",
  (evt) => {
    const { x, y } = paper.clientToLocalPoint(evt.clientX, evt.clientY);
    const i = Math.floor(x / GRID_SIZE);
    const j = Math.floor(y / GRID_SIZE);
    drawCoordinates(paper, i, j);
  },
  false
);

drawGrid(paper);
drawCoordinates(paper, 0, 0);

// 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, GRID_SIZE)
      );
    }
  });

// A function to draw the grid.
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);
}

// A function to highlight a point in the grid
function drawCoordinates(paper, i, j) {
  textVEl.text(`x: ${i}  y: ${j}`, { verticalTextAnchor: "middle" });
  gVEl.attr("transform", `translate(${i * GRID_SIZE},${j * GRID_SIZE})`);
  if (i >= 0 && j >= 0 && i < GRID_COUNT && j < GRID_COUNT) {
  if (!gVEl.node.isConnected) {
    gVEl.appendTo(paper.getLayerNode(dia.Paper.Layers.BACK));
  }
  } else {
    gVEl.remove();
  }
}

External CSS

  1. https://cdn.jsdelivr.net/npm/jointjs@3.7.2/dist/joint.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.1/backbone-min.js
  4. https://cdn.jsdelivr.net/npm/jointjs@3.7.2/dist/joint.min.js