Appearance
React
This example shows how to integrate Ogma with React using the ogma-react library.
tsx
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
import { createRoot } from 'react-dom/client';
import App from './App';
import './styles.css';
const root = createRoot(document.getElementById('app'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="app"></div>
<!-- React -->
<script src="./index.tsx"></script>
</body>
</html>
css
html,
body {
height: 100%;
margin: 0;
padding: 0;
font-family: Georgia, 'Times New Roman', Times, serif;
}
:root {
--overlay-background-color: #282c34;
--overlay-text-color: #61dafb;
}
#app {
height: 100%;
}
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
}
.ogma-tooltip {
z-index: 401;
box-sizing: border-box;
}
.ogma-tooltip--content {
transform: translate(-50%, 0);
background-color: var(--overlay-background-color);
color: var(--overlay-text-color);
border-radius: 5px;
padding: 5px;
box-sizing: border-box;
box-shadow: 0 8px 30px rgb(0 0 0 / 12%);
width: auto;
height: auto;
position: relative;
}
.ogma-tooltip {
transition: linear;
transition-property: transform;
transition-duration: 50ms;
pointer-events: none;
}
.ogma-tooltip--content:after {
content: '';
width: 0;
height: 0;
border-style: solid;
border-width: 6px 7px 6px 0;
border-color: transparent var(--overlay-background-color) transparent
transparent;
position: absolute;
left: 50%;
top: auto;
bottom: 3px;
right: auto;
transform: translate(-50%, 100%) rotate(270deg);
}
.ogma-tooltip--top .ogma-tooltip--content {
bottom: 6px;
transform: translate(-50%, -100%);
}
.ogma-tooltip--bottom .ogma-tooltip--content {
transform: translate(-50%, 0%);
top: 3px;
}
.ogma-tooltip--bottom .ogma-tooltip--content:after {
top: 3px;
bottom: auto;
transform: translate(-50%, -100%) rotate(90deg);
}
.ogma-tooltip--right .ogma-tooltip--content {
transform: translate(0, -50%);
left: 6px;
}
.ogma-tooltip--right .ogma-tooltip--content:after {
left: 0%;
top: 50%;
transform: translate(-100%, -50%) rotate(0deg);
}
.ogma-tooltip--left .ogma-tooltip--content {
transform: translate(-100%, -50%);
right: 6px;
}
.ogma-tooltip--left .ogma-tooltip--content:after {
right: 0%;
left: auto;
top: 50%;
transform: translate(100%, -50%) rotate(180deg);
}
.info {
color: white;
}
json
{
"dependencies": {
"react": "18.3.1",
"react-dom": "18.3.1",
"@linkurious/ogma-react": "5.0.3"
}
}
tsx
import React, { useState, useEffect } from 'react';
import Ogma, { RawGraph, Node, Edge } from '@linkurious/ogma';
// avoid name clash
import { Ogma as Vis, Tooltip, Point } from '@linkurious/ogma-react';
import { LayoutService } from './Layout';
import { Styles } from './Styles';
import { parse } from './parse';
export default function App() {
// graph state
const [graph, setGraph] = useState<RawGraph>();
const [loading, setLoading] = useState(true);
const [target, setTarget] = useState<Node | Edge | null>();
const [tooltipPositon, setTooltipPosition] = useState<Point>({
x: -1e5,
y: -1e5
});
useEffect(() => {
// load custom json and parse it
Ogma.parse
.jsonFromUrl('files/tokyo-subway.json', parse)
// store it in the component state
.then(json => setGraph(json))
.then(() => setLoading(false));
}, []);
const onReady = (ogma: Ogma) => {
ogma.events.on('mousemove', () => {
const ptr = ogma.getPointerInformation();
setTooltipPosition(
ogma.view.screenToGraphCoordinates({ x: ptr.x, y: ptr.y })
);
setTarget(ptr.target);
});
};
if (loading) return <div>Loading...</div>;
return (
<Vis graph={graph} onReady={onReady}>
<LayoutService />
<Styles />
<Tooltip visible={!!target} placement="left" position={tooltipPositon}>
<div className="x">
{target ? (
<span>
{target.isNode ? 'Node' : 'Edge'}
<span className="info"> {target.getId()}</span>
</span>
) : (
'nothing'
)}
</div>
</Tooltip>
</Vis>
);
}
tsx
import { useEffect } from 'react';
import { useOgma } from '@linkurious/ogma-react';
export function LayoutService() {
const ogma = useOgma(); // hook to get the ogma instance
useEffect(() => {
const onNodesAdded = () => {
// apply layout here
ogma.layouts.force({ gravity: 0, locate: true });
};
ogma.events.on('addNodes', onNodesAdded);
if (ogma.getNodes().size > 0) onNodesAdded();
// cleanup
return () => {
ogma.events.off(onNodesAdded);
};
}, []);
return null;
}
ts
import { RawEdge, RawGraph } from '@linkurious/ogma';
export const parse = (input: any): RawGraph => {
const edges: RawEdge[] = [];
// keys are stations
const edgesVisited = new Set<string>();
const nodes = Object.keys(input).map(name => {
const node = input[name];
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);
edgesVisited.add(target + ':' + source);
// identify the line by first symbol
const code = sourceLine === targetLine ? sourceLine : null;
edges.push({
source: name,
target: connection.target_id,
data: { code: code }
});
}
});
return { id: name, data: node };
});
return { nodes, edges };
};
tsx
import React from 'react';
import O, { NodeStyleRule, EdgeStyleRule } from '@linkurious/ogma-react';
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'
};
export function Styles () {
return (<>
<NodeStyleRule attributes={{
radius: 12,
color: '#ffffff',
innerStroke: {
width: 7,
color: '#505050',
scalingMethod: 'scaled',
minVisibleSize: 0
},
draggable: false,
text: {
minVisibleSize: 0,
size: 8
}
}} />
<EdgeStyleRule attributes={{
color: edge => colors[edge.getData('code')],
width: 12
}} />
</>);
}