Skip to content
  1. Examples

Blueprint new

This example shows how to combine graph data with a blueprint or a schematic using the layers API, thus making it interactive. Switch between different views to change the layout and the background of the graph.

ts
import Ogma, { CanvasLayer } from '@linkurious/ogma';
import { loadBlueprint, drawBlueprint } from './render';
import { positions, projection } from './position';

// blueprint view modes
const VIEW_MODES = ['side', 'top'];
let viewMode = VIEW_MODES[0];
let drawLayer: CanvasLayer;

// ogma & style rules
const ogma = new Ogma({
  container: 'graph-container',
  options: { backgroundColor: '#2555aa' }
});

const labelStyles = {
  color: '#fff',
  backgroundColor: '#000',
  margin: 8,
  padding: 4
};
// regular styles
ogma.styles.addRule({
  nodeAttributes: {
    text: labelStyles
  }
});
// hovered styles
ogma.styles.setHoveredNodeAttributes({
  text: labelStyles
});

// load the blueprint
Ogma.parse
  .jsonFromUrl('elements.json')
  .then(graph => ogma.setGraph(graph))
  .then(() => loadBlueprint())
  .then(image => {
    drawLayer = ogma.layers.addCanvasLayer(ctx =>
      drawBlueprint(ctx, image, viewMode === 'side')
    );
    return drawLayer.moveToBottom();
  })
  .then(() => onViewModeChange())
  .then(() => ogma.view.locateGraph())
  .then(() => ogma.view.setZoom(0.4));

// handle view mode changes
const form = document.getElementById('wrapper') as HTMLFormElement;
form.addEventListener('change', () => setTimeout(onFormChange));

const onViewModeChange = () => {
  // update the annotation layers
  drawLayer?.refresh();

  const sideView = viewMode === 'side';
  const nodes = ogma.getNodes();
  // update nodes positions
  nodes.setAttributes(
    // use the right coordinates from the array depending on the view mode
    nodes.map(node => projection(positions[node.getId() as number], sideView))
  );
};

const onFormChange = () => {
  viewMode =
    VIEW_MODES[
      Number(
        Array.prototype.filter.call(form['layering'], input => {
          return input.checked;
        })[0].value
      )
    ];
  onViewModeChange();
};
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />

    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="graph-container"></div>
    <form id="wrapper" class="control-bar">
      <p>Choose the view:</p>
      <label
        ><input type="radio" name="layering" checked value="0" /> Side</label
      >
      <label><input type="radio" name="layering" value="1" /> Top</label>
    </form>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}
#wrapper {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 400;
  background: white;
  padding: 10px;
}

.control-bar {
  font-family: Helvetica, Arial, sans-serif;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
  border-radius: 4px;
}
.control-bar label {
  display: block;
}
.concept {
  height: 10px;
  width: 10px;
  background-color: #2ca02c;
  border-radius: 50%;
  display: inline-block;
}
.table {
  height: 10px;
  width: 10px;
  background-color: #ff9896;
  border-radius: 50%;
  display: inline-block;
}
json
{
  "nodes": [
    {
      "id": 0,
      "attributes": {
        "x": 2995,
        "y": 300,
        "color": "#2ca02c",
        "radius": 50,
        "shape": "circle",
        "text": "Front Right Wheel"
      },
      "x": 421.66666666666674,
      "y": 0,
      "r": 80,
      "layer": 0,
      "sink": false
    },
    {
      "id": 1,
      "attributes": {
        "x": 2667.5,
        "y": 200,
        "color": "#ff7f0e",
        "radius": 50,
        "shape": "circle",
        "text": "Front Left Wheel"
      },
      "x": 337.33333333333337,
      "y": 99.47145877378435,
      "r": 80,
      "sink": false
    },
    {
      "id": 2,
      "attributes": {
        "x": 2995,
        "y": 300,
        "color": "#2ca02c",
        "radius": 50,
        "shape": "circle",
        "text": "Back Right Wheel"
      },
      "x": 421.66666666666674,
      "y": 0,
      "r": 80,
      "layer": 0,
      "sink": false
    },
    {
      "id": 3,
      "attributes": {
        "x": 2667.5,
        "y": 200,
        "color": "#ff7f0e",
        "radius": 50,
        "shape": "circle",
        "text": "Back Left Wheel"
      },
      "x": 337.33333333333337,
      "y": 99.47145877378435,
      "r": 80,
      "sink": false
    },
    {
      "id": 4,
      "attributes": {
        "x": 3127.5,
        "y": 250,
        "color": "#1f77b4",
        "radius": 50,
        "shape": "circle",
        "text": "Driver"
      },
      "x": 506,
      "y": 99.47145877378435,
      "r": 80,
      "sink": false
    },
    {
      "id": 5,
      "attributes": {
        "x": 3127.5,
        "y": 250,
        "color": "#1f00b4",
        "radius": 50,
        "shape": "circle",
        "text": "Shovel Top Corner"
      },
      "x": 506,
      "y": 99.47145877378435,
      "r": 80,
      "sink": false
    },
    {
      "id": 6,
      "attributes": {
        "x": 3127.5,
        "y": 250,
        "color": "#1f00b4",
        "radius": 50,
        "shape": "circle",
        "text": "Shovel Bottom Corner"
      },
      "x": 506,
      "y": 99.47145877378435,
      "r": 80,
      "sink": false
    }
  ],
  "edges": []
}
ts
import { Point } from '@linkurious/ogma';

export type Vector<N extends number, T = [number, ...Array<number>]> = T & {
  readonly length: N;
};

/**
 * Blueprint element positions in the 3D real-world space.
 */
export const positions: Vector<3>[] = [
  // wheel positions
  [700, 740, 300],
  [700, 740, 760],
  [1450, 740, 300],
  [1450, 740, 760],
  // driver position
  [1080, 340, 530],
  // shovel
  [200, 850, 400],
  [400, 900, 650]
];

/**
 * Project the blueprint element 3D positions into the 2D graph space.
 */
export const projection = ([x, y, z]: Vector<3>, side: boolean): Point =>
  side ? { x, y } : { x, y: z };
ts
/**
 * Load the original blueprint image.
 */
export const loadBlueprint = (): Promise<HTMLImageElement> =>
  new Promise(resolve => {
    const image = new Image(3470, 2301); // using optional size for image
    image.onload = () => resolve(image); // resolve when image has loaded
    image.src = 'img/blueprint.png';
  });

/**
 * Draw the original blueprint image.
 */
export const drawBlueprint = (
  ctx: CanvasRenderingContext2D,
  image: HTMLImageElement,
  side: boolean
) => {
  const dx = 280;
  const dy = side ? 200 : 1130;
  const dWidth = 1960;
  const dHeight = 930;
  ctx.drawImage(image, dx, dy, dWidth, dHeight, 0, 0, dWidth, dHeight);
};