Skip to content
  1. Examples

Indoor maps

This example shows how to use custom coordinate system to display nodes on a plan or a map in geo mode.

js
import Ogma from '@linkurious/ogma';

Ogma.libraries['leaflet'] = L;

const graph = {
  nodes: [
    { id: 'Router', data: { floorPlanY: 36, floorPlanX: 953 } },
    { id: 'Web Server', data: { floorPlanY: 72, floorPlanX: 903 } },
    { id: 'Switch', data: { floorPlanY: 476, floorPlanX: 953 } },
    { id: 'Admin Hub', data: { floorPlanY: 478, floorPlanX: 497 } },
    { id: 'Admin Workstation 1', data: { floorPlanY: 545, floorPlanX: 658 } },
    { id: 'Admin Workstation 2', data: { floorPlanY: 545, floorPlanX: 497 } },
    { id: 'Admin Workstation 3', data: { floorPlanY: 545, floorPlanX: 318 } },
    { id: 'Network Printer 1', data: { floorPlanY: 36, floorPlanX: 827 } },
    { id: 'Network Printer 2', data: { floorPlanY: 36, floorPlanX: 320 } },
    { id: 'Internet', data: { floorPlanY: 103, floorPlanX: 991 } },
    { id: 'Workstation 1', data: { floorPlanY: 334, floorPlanX: 335 } },
    { id: 'Workstation 2', data: { floorPlanY: 334, floorPlanX: 425 } },
    { id: 'Workstation 3', data: { floorPlanY: 294, floorPlanX: 392 } },
    { id: 'Workstation 4', data: { floorPlanY: 294, floorPlanX: 483 } },
    { id: 'Workstation 5', data: { floorPlanY: 334, floorPlanX: 554 } },
    { id: 'Workstation 6', data: { floorPlanY: 294, floorPlanX: 609 } },
    { id: 'Workstation 7', data: { floorPlanY: 334, floorPlanX: 645 } },
    { id: 'Workstation 8', data: { floorPlanY: 294, floorPlanX: 698 } },
    { id: 'Workstation 9', data: { floorPlanY: 334, floorPlanX: 762 } },
    { id: 'Workstation 10', data: { floorPlanY: 294, floorPlanX: 826 } },
    { id: 'Workstation 11', data: { floorPlanY: 334, floorPlanX: 859 } },
    { id: 'Workstation 12', data: { floorPlanY: 294, floorPlanX: 917 } },
    { id: 'Workstation 13', data: { floorPlanY: 151, floorPlanX: 552 } },
    { id: 'Workstation 14', data: { floorPlanY: 111, floorPlanX: 608 } },
    { id: 'Workstation 15', data: { floorPlanY: 151, floorPlanX: 642 } },
    { id: 'Workstation 16', data: { floorPlanY: 111, floorPlanX: 697 } },
    { id: 'Isle 1 Switch', data: { floorPlanY: 314, floorPlanX: 412 } },
    { id: 'Isle 2 Switch', data: { floorPlanY: 314, floorPlanX: 628 } },
    { id: 'Isle 3 Switch', data: { floorPlanY: 314, floorPlanX: 840 } },
    { id: 'Isle 4 Switch', data: { floorPlanY: 129, floorPlanX: 628 } },
    { id: 'Ethernet Switch', data: { floorPlanY: 194, floorPlanX: 953 } }
  ],
  edges: [
    {
      id: 'Admin to Hub 1',
      source: 'Admin Workstation 1',
      target: 'Admin Hub',
      attributes: { width: 1 }
    },
    {
      id: 'Admin to Hub 2',
      source: 'Admin Workstation 2',
      target: 'Admin Hub',
      attributes: { width: 1 }
    },
    {
      id: 'Admin to Hub 3',
      source: 'Admin Workstation 3',
      target: 'Admin Hub',
      attributes: { width: 1 }
    },
    {
      id: 'Server to Router',
      source: 'Web Server',
      target: 'Router',
      attributes: { width: 5 }
    },
    {
      id: 'Admin to Switch',
      source: 'Admin Hub',
      target: 'Switch',
      attributes: { width: 5 }
    },
    {
      id: 'Switch to Internet',
      source: 'Switch',
      target: 'Router',
      attributes: { width: 5 }
    },
    {
      id: 'Printer 1 Linking',
      source: 'Network Printer 1',
      target: 'Router',
      attributes: { width: 1 }
    },
    {
      id: 'Printer 2 Linking',
      source: 'Network Printer 2',
      target: 'Router',
      attributes: { width: 1 }
    },
    {
      id: 'W1 to Switch',
      source: 'Workstation 1',
      target: 'Isle 1 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W2 to Switch',
      source: 'Workstation 2',
      target: 'Isle 1 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W3 to Switch',
      source: 'Workstation 3',
      target: 'Isle 1 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W4 to Switch',
      source: 'Workstation 4',
      target: 'Isle 1 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W5 to Switch',
      source: 'Workstation 5',
      target: 'Isle 2 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W6 to Switch',
      source: 'Workstation 6',
      target: 'Isle 2 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W7 to Switch',
      source: 'Workstation 7',
      target: 'Isle 2 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W8 to Switch',
      source: 'Workstation 8',
      target: 'Isle 2 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W9 to Switch',
      source: 'Workstation 9',
      target: 'Isle 3 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W10 to Switch',
      source: 'Workstation 10',
      target: 'Isle 3 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W11 to Switch',
      source: 'Workstation 11',
      target: 'Isle 3 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W12 to Switch',
      source: 'Workstation 12',
      target: 'Isle 3 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W13 to Switch',
      source: 'Workstation 13',
      target: 'Isle 4 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W14 to Switch',
      source: 'Workstation 14',
      target: 'Isle 4 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W15 to Switch',
      source: 'Workstation 15',
      target: 'Isle 4 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'W16 to Switch',
      source: 'Workstation 16',
      target: 'Isle 4 Switch',
      attributes: { width: 1 }
    },
    {
      id: 'Isle 1 to Switch',
      source: 'Isle 1 Switch',
      target: 'Ethernet Switch',
      attributes: { width: 3 }
    },
    {
      id: 'Isle 2 to Switch',
      source: 'Isle 2 Switch',
      target: 'Ethernet Switch',
      attributes: { width: 3 }
    },
    {
      id: 'Isle 3 to Switch',
      source: 'Isle 3 Switch',
      target: 'Ethernet Switch',
      attributes: { width: 3 }
    },
    {
      id: 'Isle 4 to Switch',
      source: 'Isle 4 Switch',
      target: 'Ethernet Switch',
      attributes: { width: 3 }
    },
    {
      id: 'Office To Internet',
      source: 'Ethernet Switch',
      target: 'Router',
      attributes: { width: 5 }
    },
    {
      id: 'To Internet',
      source: 'Router',
      target: 'Internet',
      attributes: { width: 10 }
    }
  ]
};

const ogma = new Ogma({
  graph: graph,
  container: 'graph-container'
});

const computerIcon = '\uf108';
const internetIcon = '\uf0c2';
const routerIcon = '\uf277';
const switchIcon = '\uf362';
const printerIcon = '\uf02f';
const serverIcon = '\uf233';

ogma.styles.addNodeRule({
  radius: 10,
  text: {
    content: node => node.getId(),
    backgroundColor: 'rgba(255, 255, 255, 0.2)',
    minVisibleSize: 0
  },
  color: 'white',
  outerStroke: {
    color: '#2171b5',
    width: 1
  },
  icon: {
    content: node => {
      const name = node.getId();
      if (/switch/i.test(name)) {
        return switchIcon;
      }
      if (/workstation/i.test(name)) {
        return computerIcon;
      }
      if (/printer/i.test(name)) {
        return printerIcon;
      }
      if (/router/i.test(name)) {
        return routerIcon;
      }
      if (/internet/i.test(name)) {
        return internetIcon;
      }
      return serverIcon;
    },
    font: 'Font Awesome 5 Free',
    color: '#2171b5',
    style: 'bold',
    minVisibleSize: 0
  }
});

ogma.styles.addEdgeRule({
  color: edge => {
    const width = edge.getAttribute('width');
    if (width === 1) {
      return '#bae4b3';
    }
    if (width < 5) {
      return '#a1dab4';
    }
    if (width < 10) {
      return '#41b6c4';
    }
    return '#225ea8';
  }
});

ogma.layouts.force({ locate: true });

// these are the real dimensions of the image
const floorPlanWidth = 3012;
const floorPlanHeight = 1984;
const floorPlanUrl = 'img/office-floorplan.png';

// L.CRS.Simple world maps a world bound of 1000 units per side:
// because the image is not squared we need to map the height
// based on its ratio proportion
const ratio = floorPlanWidth / floorPlanHeight;
const bounds = L.latLngBounds([
  [0, 0],
  [1000 / ratio, 1000]
]);

const toggleTopology = () =>
  ogma.geo
    .toggle({
      // note that in L.CRS.Simple x and y are
      // longitude and latitude, respectively
      longitudePath: 'floorPlanX',
      latitudePath: 'floorPlanY',
      // set the custom layer as tiles object
      tiles: L.imageOverlay(floorPlanUrl, bounds),
      // Use a custom projection function
      crs: L.CRS.Simple,
      // Do not wrap coordinates: let the user to handle it
      wrapCoordinates: false,
      // when using custom projections zoom level can be negative too
      minZoomLevel: -2,
      // Transition time from Graph to Geo mode
      duration: 1000
    })
    .then(() => {
      if (ogma.geo.enabled()) {
        const map = ogma.geo.getMap();
        map.fitBounds(bounds);
      }
    });

document.querySelector('#ui').addEventListener('change', evt => {
  toggleTopology();
});
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.3/leaflet.css"
    />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.3/leaflet.js"></script>

    <link
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/solid.min.css"
      rel="stylesheet"
    />
    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>

  <body>
    <!-- we force the loading of the font awesome -->
    <i class="fas fa-camera fa-1x" style="color: rgba(0, 0, 0, 0)"></i>
    <div id="graph-container"></div>
    <div class="control-bar" id="controls">
      <form id="ui">
        <span>Topology View</span>
        <label
          ><input
            type="radio"
            name="topology"
            value="logical"
            checked="checked"
          />
          <i>Logical Topology</i></label
        >
        <label
          ><input type="radio" name="topology" value="physical" />
          <i>Physical Topology</i></label
        >
      </form>
    </div>
    <script src="index.js"></script>
  </body>
</html>
css
html,
body {
  margin: 0;
}

#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}

.control-bar {
  font-family: Helvetica, Arial, sans-serif;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
  border-radius: 4px;
  background: white;
  padding: 5px 10px;
}

#controls {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 9999;
}

#controls label {
  display: block;
}

#controls form {
  padding-top: 5px;
  line-height: 1.5em;
}