Appearance
Custom JSON
This example shows how you can pre-process the imported JSON in case when you for some reason cannot do it at an earlier stage. This is especially convenient for prototyping or static graphs. Click on any node to see its raw JSON shape
ts
import Ogma, { RawEdge, RawGraph } from '@linkurious/ogma';
type LineCode = keyof typeof colors;
interface EdgeData {
code: LineCode | null;
}
interface NodeData {}
const ogma = new Ogma<NodeData, EdgeData>({
container: 'graph-container'
});
// data from https://en.wikipedia.org/wiki/Tokyo_subway
const colors = {
G: '#F9A328',
M: '#EE2628',
H: '#C7BEB3',
T: '#00AFEF',
C: '#1BB267',
Y: '#D1A662',
Z: '#8C7DBA',
N: '#02B69B',
F: '#A95E33',
A: '#EF463C',
I: '#0072BC',
S: '#ABBA41',
E: '#CE1C64'
};
ogma.styles.addRule({
edgeAttributes: {
color: edge => colors[edge.getData('code')!],
width: 12
//outline: true
},
nodeAttributes: {
radius: 12,
color: '#ffffff',
innerStroke: {
width: 7,
color: '#505050',
scalingMethod: 'scaled',
minVisibleSize: 0
},
draggable: false,
text: {
minVisibleSize: 0,
size: 8
}
}
});
const customJsonToRawGraph = (input: any): RawGraph<NodeData, EdgeData> => {
const edges: RawEdge<EdgeData>[] = [];
// keys are stations
const edgesVisited = new Set<string>();
const nodes = Object.keys(input).map(name => {
const node = input[name];
// @ts-expect-error
node.connections.forEach(connection => {
const source = name;
const target = connection.target_id;
// read line codes
const sourceLine = source.substring(0, 1);
const targetLine = target.substring(0, 1);
// make edges bi-directional
if (
!edgesVisited.has(source + ':' + target) &&
!edgesVisited.has(target + ':' + source)
) {
edgesVisited.add(source + ':' + target);
// identify the line by first symbol
const code =
sourceLine === targetLine ? (sourceLine as LineCode) : null;
edges.push({
source: name,
target: connection.target_id,
data: { code }
});
}
});
return { id: name, data: node };
});
return { nodes, edges };
};
ogma.tools.tooltip.onNodeClick(
node => {
return (
'<div class="arrow"></div>' +
'<div class="ogma-tooltip-header">' +
node.getId() +
'</div>' +
'<div class="ogma-tooltip-body">' +
'<code><pre>' +
JSON.stringify(node.getData(), undefined, 2) +
'</pre></code>' +
'</div>'
);
},
{ className: 'ogma-tooltip' }
);
Ogma.parse
.jsonFromUrl('tokyo-subway.json', customJsonToRawGraph)
.then(graph => ogma.setGraph(graph))
.then(() => {
return ogma.layouts.force({
gravity: 0,
edgeStrength: 1,
locate: true
});
})
.then(() => {
console.log('import done');
const indicator = document.getElementById('info')!;
indicator.parentElement!.removeChild(indicator);
});
ogma.tools.brand.set(
'<div class="attribution">' +
'<img src="img/tokyo-metro.png" class="logo">' +
'<h1>Tokyo Subway</h1>' +
'<p>Sources: <a href="https://github.com/Jugendhackt/tokyo-metro-data/" target="_blank">graph</a>, <a href="https://en.wikipedia.org/wiki/Tokyo_subway" target="_blank">colors</a></p>' +
'</div>',
{
position: 'bottom-right',
className: 'myCssClass'
}
);
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,700"
rel="stylesheet"
type="text/css"
/>
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="graph-container"></div>
<div id="info">loading...</div>
<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;
}
#info {
position: absolute;
color: #fff;
background: #141229;
font-size: 12px;
font-family: monospace;
padding: 5px;
top: 0;
left: 0;
}
.attribution {
font-family: "Roboto", Helvetica, sans-serif;
padding: 10px;
text-align: center;
font-size: 0.75em;
}
.attribution h1 {
font-size: 1.5em;
}
.attribution .logo {
width: 80px;
height: 80px;
}
.ogma-tooltip {
max-width: 240px;
max-height: 280px;
background-color: #fff;
border: 1px solid #999;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
border-radius: 6px;
cursor: auto;
font-family: Arial;
font-size: 12px;
}
.ogma-tooltip-header {
font-variant: small-caps;
font-size: 120%;
color: #000;
border-bottom: 1px solid #999;
padding: 10px;
}
.ogma-tooltip-body {
padding: 10px;
overflow-x: hidden;
overflow-y: auto;
max-width: inherit;
max-height: 180px;
background: #ddd;
}
.ogma-tooltip-footer {
padding: 10px;
border-top: 1px solid #999;
}
.ogma-tooltip > .arrow {
border-width: 10px;
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.ogma-tooltip.top {
margin-top: -12px;
}
.ogma-tooltip.top > .arrow {
left: 50%;
bottom: -10px;
margin-left: -10px;
border-top-color: #999;
border-bottom-width: 0;
}
.ogma-tooltip.bottom {
margin-top: 12px;
}
.ogma-tooltip.bottom > .arrow {
left: 50%;
top: -10px;
margin-left: -10px;
border-bottom-color: #999;
border-top-width: 0;
}
.ogma-tooltip.left {
margin-left: -12px;
}
.ogma-tooltip.left > .arrow {
top: 50%;
right: -10px;
margin-top: -10px;
border-left-color: #999;
border-right-width: 0;
}
.ogma-tooltip.right {
margin-left: 12px;
}
.ogma-tooltip.right > .arrow {
top: 50%;
left: -10px;
margin-top: -10px;
border-right-color: #999;
border-left-width: 0;
}
json
{
"A01": {
"connections": [
{
"duration": 3,
"target_id": "A02",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A02": {
"connections": [
{
"duration": 3,
"target_id": "A01",
"type": "ride"
},
{
"duration": 1,
"target_id": "A03",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A03": {
"connections": [
{
"duration": 1,
"target_id": "A02",
"type": "ride"
},
{
"duration": 2,
"target_id": "A04",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A04": {
"connections": [
{
"duration": 2,
"target_id": "A03",
"type": "ride"
},
{
"duration": 2,
"target_id": "A05",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A05": {
"connections": [
{
"duration": 2,
"target_id": "A04",
"type": "ride"
},
{
"duration": 2,
"target_id": "A06",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A06": {
"connections": [
{
"duration": 2,
"target_id": "A05",
"type": "ride"
},
{
"duration": 2,
"target_id": "A07",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A07": {
"connections": [
{
"duration": 2,
"target_id": "A06",
"type": "ride"
},
{
"duration": 3,
"target_id": "A08",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A08": {
"connections": [
{
"duration": 3,
"target_id": "A07",
"type": "ride"
},
{
"duration": 2,
"target_id": "A09",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A09": {
"connections": [
{
"duration": 2,
"target_id": "A08",
"type": "ride"
},
{
"duration": 2,
"target_id": "A10",
"type": "ride"
},
{
"duration": 3,
"target_id": "E20",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A10": {
"connections": [
{
"duration": 2,
"target_id": "A09",
"type": "ride"
},
{
"duration": 2,
"target_id": "A11",
"type": "ride"
},
{
"duration": 3,
"target_id": "G08",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A11": {
"connections": [
{
"duration": 2,
"target_id": "A10",
"type": "ride"
},
{
"duration": 2,
"target_id": "A12",
"type": "ride"
},
{
"duration": 3,
"target_id": "H09",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A12": {
"connections": [
{
"duration": 2,
"target_id": "A11",
"type": "ride"
},
{
"duration": 1,
"target_id": "A13",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A13": {
"connections": [
{
"duration": 1,
"target_id": "A12",
"type": "ride"
},
{
"duration": 2,
"target_id": "A14",
"type": "ride"
},
{
"duration": 2,
"target_id": "T10",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A14": {
"connections": [
{
"duration": 2,
"target_id": "A13",
"type": "ride"
},
{
"duration": 2,
"target_id": "A15",
"type": "ride"
},
{
"duration": 3,
"target_id": "H13",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A15": {
"connections": [
{
"duration": 2,
"target_id": "A14",
"type": "ride"
},
{
"duration": 1,
"target_id": "A16",
"type": "ride"
},
{
"duration": 3,
"target_id": "S09",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A16": {
"connections": [
{
"duration": 1,
"target_id": "A15",
"type": "ride"
},
{
"duration": 1,
"target_id": "A17",
"type": "ride"
}
],
"name_en": null,
"name_jp": null
},
"A17": {
"connections": [
{
"duration": 1,
"target_id": "A16",
"type": "ride"
},
{
"duration": 1,
"target_id": "A18",
"type": "ride"
},
{
"duration": 3,
"target_id": "E11",
"type": "walk"
}
],
"name_en": null,
"name_jp": null
},
"A18": {
"connections": [
{
"d
...