Appearance
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;
}