Skip to content
  1. Examples

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
      }} />
    </>);
}