Appearance
Fuzzy search 
This example shows how to implement a searchbar to find nodes in the graph.
ts
import Ogma, { NodeId, RawGraph } from '@linkurious/ogma';
type NodeData = {
  latin_name: string;
};
const ogma = new Ogma<NodeData>({
  container: 'graph-container'
});
// add highlight style rule
ogma.styles.createClass({
  name: 'highlighted',
  nodeAttributes: {
    radius: function (node) {
      return Number(node.getAttribute('radius')) * 1.5;
    },
    halo: {
      color: '#cccccc',
      width: 20
    }
  }
});
type SearchResult = {
  index: number;
  string: string;
  original: {
    id: string;
    data: {
      latin_name: string;
    };
  };
  score: number;
};
const highlightNode = function (id: NodeId) {
  ogma.getNodes().removeClass('highlighted');
  if (id) ogma.getNode(id)!.addClass('highlighted');
};
const selectNode = function (id: NodeId) {
  ogma.getSelectedNodes().setSelected(false);
  if (id) ogma.getNode(id)!.setSelected(true);
};
const search = {
  data: { nodes: [], edges: [] } as RawGraph,
  query: (query: string) => {}
};
const init = function (graph: RawGraph) {
  const resultsContainer = document.querySelector('#results') as HTMLElement;
  let currentItem = 0; // current item selected in the list
  const input = document.querySelector('#search-input') as HTMLInputElement;
  // store raw data
  search.data = graph;
  // expose search procedure
  search.query = function (value: string) {
    // perform query using fuzzy.js
    const results = fuzzy.filter(value, search.data.nodes, {
      // highlight the matching substring
      pre: '<span class="match">',
      post: '</span>',
      extract: (item: { data: NodeData }) => item.data.latin_name
    });
    // render the list HTML
    resultsContainer.innerHTML =
      '<div class="dropdown-menu">' +
      results
        .map(
          (result: SearchResult, i: number) =>
            `<div
          class="dropdown-item ${i === currentItem ? 'hover' : ''}
          data-position="${i}"
          data-index="${result.index}"
          data-value="${result.original.data.latin_name}"
          data-id="${result.original.id}">${result.string}</div>`
        )
        .join('') +
      '</div>';
    highlightNode(results[currentItem].original.id);
  };
  // listen for input
  input.addEventListener('keyup', function (evt) {
    if (evt.keyCode === 40) {
      // down arrow
      currentItem++;
    } else if (evt.keyCode === 38) {
      // up arrow
      currentItem--;
    } else if (evt.keyCode === 13) {
      // enter
      const item =
        resultsContainer.querySelectorAll('.dropdown-item')[currentItem];
      input.value = item.getAttribute('data-value')!;
      selectNode(item.getAttribute('data-id')!);
    }
    // wait for the next frame to throttle the search
    requestAnimationFrame(() => {
      search.query(input.value);
    });
  });
  // highlight on hover
  resultsContainer.addEventListener('mousemove', evt => {
    requestAnimationFrame(() => {
      const target = evt.target as HTMLElement;
      const id = target.getAttribute('data-id')!;
      currentItem = Number(target.getAttribute('data-position') || 0);
      highlightNode(id);
    });
  });
  // select on click
  resultsContainer.addEventListener('click', evt => {
    const target = evt.target as HTMLElement;
    const id = target.getAttribute('data-id')!;
    currentItem = Number(target.getAttribute('data-position') || 0);
    input.value = target.getAttribute('data-value')!;
    selectNode(id);
  });
  return graph;
};
Ogma.parse
  .jsonFromUrl<NodeData>('paris-metro.json') // load data
  .then(init) // init search
  .then(graph => ogma.setGraph(graph)) // add graph to ogma
  .then(() => ogma.view.locateGraph()) // position
  .then(() => {
    // trigger up the search
    const input = document.querySelector('#search-input')! as HTMLInputElement;
    input.value = 'Lou';
    search.query('Lou');
    input.focus();
  });html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <script src="https://cdn.jsdelivr.net/npm/fuzzy@0.1.3/lib/fuzzy.js"></script>
    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="graph-container"></div>
    <div class="toolbar">
      <input type="search" placeholder="Search station" id="search-input" />
      <div id="results" class="dropdown"></div>
    </div>
    <script type="module" src="index.ts"></script>
  </body>
</html>css
html,
body {
  padding: 0;
  margin: 0;
  font: normal 18px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}
.toolbar {
  display: block;
  position: absolute;
  top: 20px;
  right: 20px;
  padding: 10px;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
  border-radius: 4px;
  background: #ffffff;
  color: #222222;
  font-weight: 300;
  min-width: 35%;
}
#search-input {
  width: 100%;
  padding: 1rem;
  font-size: 1rem;
}
#search-input:focus {
  outline: 0;
}
.match {
  font-weight: bold;
  text-decoration: underline;
}
.dropdown {
  position: relative;
}
.dropdown-item {
  display: block;
  width: 100%;
  padding: 0.25rem 1.5rem;
  clear: both;
  font-weight: 400;
  color: #212529;
  text-align: inherit;
  white-space: nowrap;
  background-color: transparent;
  border: 0;
  cursor: pointer;
  box-sizing: border-box;
}
.dropdown-item:hover,
.dropdown-item.hover {
  background: #dddddd;
}
.dropdown-menu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 1000;
  display: block;
  float: left;
  min-width: 10rem;
  padding: 0.5rem 0;
  margin: 0.125rem 0 0;
  font-size: 1rem;
  color: #212529;
  text-align: left;
  list-style: none;
  background-color: #fff;
  background-clip: padding-box;
  border: 1px solid rgba(0, 0, 0, 0.15);
  border-radius: 0.25rem;
  max-height: 500px;
  overflow-y: auto;
}json
{
  "nodes": [
    {
      "degree": 5,
      "id": "Gare de LyonPARIS-12EME",
      "inDegree": 3,
      "outDegree": 2,
      "active": false,
      "halo": false,
      "hidden": false,
      "latitude": 48.844757,
      "longitude": 2.3740723,
      "pinned": false,
      "size": 42.42640687119285,
      "text": "Gare de Lyon",
      "x": 525.1153564453125,
      "y": -281,
      "isNode": true,
      "data": {
        "latin_name": "Gare de Lyon",
        "Eccentricity": 23,
        "Betweenness Centrality": 66284.82695915208,
        "Closeness Centrality": 8.378731343283581,
        "local_name": "Gare de Lyon",
        "location": "PARIS-12EME",
        "Eigenvector Centrality": 1,
        "lines": "Line14-Line1-RERA-RERD",
        "latitude": 48.844757,
        "longitude": 2.3740723
      },
      "attributes": {
        "radius": 42.42640687119285,
        "color": [
          "#67328E",
          "#F2C931"
        ],
        "text": "Gare de Lyon"
      }
    },
    {
      "degree": 3,
      "id": "Gare du NordPARIS-10EME",
      "inDegree": 2,
      "outDegree": 1,
      "active": false,
      "halo": false,
      "hidden": false,
      "latitude": 48.880035,
      "longitude": 2.3545492,
      "pinned": false,
      "size": 42.42640687119285,
      "text": "Gare du Nord",
      "x": 496.4261779785156,
      "y": -1209,
      "isNode": true,
      "data": {
        "latin_name": "Gare du Nord",
        "Eccentricity": 25,
        "Betweenness Centrality": 36901.69440836936,
        "Closeness Centrality": 8.89179104477612,
        "local_name": "Gare du Nord",
        "location": "PARIS-10EME",
        "Eigenvector Centrality": 0.5366748142943254,
        "lines": "Line4-Line5-RERB-RERD",
        "latitude": 48.880035,
        "longitude": 2.3545492
      },
      "attributes": {
        "radius": 42.42640687119285,
        "color": [
          "#BB4D98",
          "#DE8B53"
        ],
        "text": "Gare du Nord"
      }
    },
    {
      "degree": 7,
      "id": "NationPARIS-12EME",
      "inDegree": 3,
      "outDegree": 4,
      "active": false,
      "halo": false,
      "hidden": false,
      "latitude": 48.848465,
      "longitude": 2.3959057,
      "pinned": false,
      "size": 60,
      "text": "Nation",
      "x": 989.2464599609375,
      "y": -235,
      "isNode": true,
      "data": {
        "latin_name": "Nation",
        "Eccentricity": 24,
        "Betweenness Centrality": 31660.579437229462,
        "Closeness Centrality": 9.078358208955224,
        "local_name": "Nation",
        "location": "PARIS-12EME",
        "Eigenvector Centrality": 0.7770134775054275,
        "lines": "Line1-Line2-Line6-Line9-RERA",
        "latitude": 48.848465,
        "longitude": 2.3959057
      },
      "attributes": {
        "radius": 60,
        "color": [
          "#F2C931",
          "#216EB4",
          "#75c695",
          "#CDC83F"
        ],
        "text": "Nation"
      }
    },
    {
      "degree": 6,
      "id": "Charles de Gaulle - EtoilePARIS-08EME",
      "inDegree": 1,
      "outDegree": 5,
      "active": false,
      "halo": false,
      "hidden": false,
      "latitude": 48.87441,
      "longitude": 2.2957628,
      "pinned": false,
      "size": 51.96152422706631,
      "text": "Charles de Gaulle - Etoile",
      "x": -973.6716918945312,
      "y": -1286,
      "isNode": true,
      "data": {
        "latin_name": "Charles de Gaulle - Etoile",
        "Eccentricity": 26,
        "Betweenness Centrality": 30386.45905483406,
        "Closeness Centrality": 9.42910447761194,
        "local_name": "Charles de Gaulle - Etoile",
        "location": "PARIS-08EME",
        "Eigenvector Centrality": 0.35464528135158707,
        "lines": "Line1-Line2-Line6-RERA",
        "latitude": 48.87441,
        "longitude": 2.2957628
      },
      "attributes": {
        "radius": 51.96152422706631,
        "color": [
          "#F2C931",
          "#216EB4",
          "#75c695"
        ],
        "text": "Charles de Gaulle - Etoile"
      }
    },
    {
      "degree": 4,
      "id": "InvalidesPARIS-07EME",
      "inDegree": 2,
      "outDegree": 2,
      "active": false,
      "halo": false,
      "hidden": false,
      "latitude": 48.862553,
      "longitude": 2.313989,
      "pinned": false,
      "size": 42.42640687119285,
      "text": "Invalides",
      "x": -689.1814575195312,
      "y": -520,
      "isNode": true,
      "data": {
        "latin_name": "Invalides",
        "Eccentricity": 25,
        "Betweenness Centrality": 22916.702586302596,
        "Closeness Centrality": 9.598880597014926,
        "local_name": "Invalides",
        "location": "PARIS-07EME",
        "Eigenvector Centrality": 0.44784291910876195,
        "lines": "Line13-Line8-RERC",
        "latitude": 48.862553,
        "longitude": 2.313989
      },
      "attributes": {
        "radius": 42.42640687119285,
        "color": [
          "#89C7D6",
          "#C5A3CA"
        ],
        "text": "Invalides"
      }
    },
    {
      "degree": 8,
      "id": "ChâteletPARIS-01ER",
      "inDegree": 0,
      "outDegree": 8,
     
...