How to handle large Datasets with

  1. Introduction: features, docs, examples
  2. Setting up the environment
  3. How to get Ogma
  4. Access your data
  5. Styling
  6. Geo mode & Transformations
  7. UI, frameworks integration

What is Ogma?

Ogma is a client-side interactive graph visualisation library

  • ogma.min.js - the library
  • ogma.min.d.ts - TypeScript declaration file
  • Documentation, tutorials & 100+ examples
  • Technical support

Documentation

https://ogma.linkurio.us

Download center

https://get.linkurio.us

Features:

Features:

Visualisation styling

Features:

Layouts

Features:

Layouts

Features:

Geo mode

Features:

Interactions

Features:

Transformations

Features:

Algorithms

1. Setting up the environment

Example: Ogma + Webpack

  • Get Node & npm https://nodejs.org/
  • npm i -D webpack webpack-cli \
    css-loader style-loader \
    webpack-dev-server html-webpack-plugin
    
  • Set up your package.json
    {
      "name": "01-setup",
      "version": "1.0.0",
      "main": "src/index.js",
      "scripts": {
        "build": "webpack",
        "start": "webpack -w"
      },
      "devDependencies": {
        "css-loader": "^4.3.0",
        "style-loader": "^1.2.1",
        "webpack": "^4.44.1",
        "webpack-cli": "^3.3.12"
      }
    }

1. Setting up the environment

Example: Ogma + Webpack

src/index.js
import "styles.css";

// your code will go here
console.log('Welcome to visualisation with Ogma');
            
src/styles.css
#container {
  display: block;
  width: 100%;
  height: 100%;
}

1. Setting up the environment

Example: Ogma + Webpack

Set up the Webpack config

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  module: {
    rules: [{
      test: /\.css$/i,
      use: ['style-loader', 'css-loader'],
    }]
  },
  entry: './src/index.js',
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  plugins: [new HtmlWebpackPlugin()],
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 9000
  }
};

1. Setting up the environment

Example: Ogma + Webpack
npm start

2. Installing Ogma

Download → copy your npm link
              npm install --save https://get.linkurio.us/.../?secret=<YOUR_API_KEY>
            
              import Ogma from 'ogma';
            

2.1 Let's show some nodes

            // index.js
import Ogma from 'ogma';

// create a container and add CSS to it
const container = document.createElement('div');
container.id = 'vis';
document.body.appendChild(container);

// initialize Ogma
const ogma = new Ogma({ container });

// add some nodes and edges
ogma.addNode({ id: 0, attributes: {
  x: -120, y: -75
}});
ogma.addNode({ id: 1, attributes: {
  x: 120, y: -75
}});
// add edge
ogma.addEdge({ source: 0, target: 1 });
            
          

2.1 Let's show some nodes

2.3 Updating Ogma version

We encourage you to update Ogma every time you get a notification that the new version is out.


  • go to get.linkurio.us
  • get a new npm link (it contains the package version)
  • copy it into your package.json

  • run npm install

3. Accessing your data

Ogma + neo4j database

For this example we have loaded a supply chain dataset from this neo4j tutorial into a hosted neo4j instance

3. Accessing your data

To load data into Ogma you need to parse it into its internal format:

{
  "nodes": [{
    "id": 0, // number|string, required
    // see https://doc.linkurio.us/ogma/latest/api.html#NodeAttributes
    "attributes": {}, // visual attributes
    "data": {}, // any data properties, free format
  }, ...],
  "edges": [{
    "source": 0, // required
    "target": 1, // required,
    // see https://doc.linkurio.us/ogma/latest/api.html#EdgeAttributes
    "attributes": {}, // same as for nodes
    "data": {}
  }]
}

See NodeAttributes, EdgeAttributes docs

3. Accessing your data

  • Load the dataset
  • Get your connection credentials
    import { HOST, DB_NAME, PASSWORD } from '../../credentials.json';
  • Connect to your database
  • Establish the API to query the graph
    const query = "MATCH (a)-[r]-() RETURN a, r";
    return this._session
      .run(query)
      // parse the response into the format Ogma understands
      .then(response => ogma.parse.neo4j(response));

3. Accessing your data

Your dummy neo4j client looks like this:

// Create a driver to connect to the Neo4j database
const driver = neo4j.driver(
  HOST, neo4j.auth.basic(DB_NAME, PASSWORD)
);
const session = driver.session({
  database: DB_NAME,
  defaultAccessMode: neo4j.session.READ,
});

// Query the graph to the server and load it to ogma
const query = "MATCH (a)-[r]-() RETURN a, r";
session
  .run(query)
  .then(response => ogma.parse.neo4j(response));

3. Accessing your data

// instance of the API client
const api = new API(HOST, DB_NAME, PASSWORD);

// load the graph
api.getGraph()
  .then((rawGraph) => ogma.setGraph(rawGraph))
  // setup the camera so we can see the entire graph
  .then(() => ogma.view.locateGraph())
  .then(() => {
    // show some information about the graph
    const nodes = ogma.getNodes();
    const edges = ogma.getEdges();
    document.getElementById('controls').innerHTML =
      `Loaded 
${nodes.size} nodes, ${edges.size} edges`; });

3. Accessing your data

4. Adding styles

4. Adding styles

What is missing?

  • Positions
  • Nodes meaning
  • Edges meaning
  • Insights

4. Adding styles

Positions

Let's run the layout!

            api.getGraph();
  .then((rawGraph) => ogma.setGraph(rawGraph))
  // apply the force layout to the graph
  // (places the nodes so it is readable )
  .then(() => ogma.layouts.force());
  

4. Adding styles

Positions

4. Adding styles

Nodes color and icon coding:

// Define buckets of importance for nodes
// some values repeat themselves for different types
const supplierStyle = { color: "#6FC1A7", icon: "\uEA04" };
const rawSupplierStyle = { color: "#3DCB8D", icon: "\uEA03" };
const wholeSalerStyle = { color: "#E77152", icon: "\uEA09" };
const retailerStyle = { color: "#9AD0D0", icon: "\uEA06" };

const nodeStyles = {
  'SupplierA': supplierStyle,
  'SupplierB': supplierStyle,

  'RawSupplierA': rawSupplierStyle,
  'RawSupplierB': rawSupplierStyle,
  'AllRawSupliers': rawSupplierStyle,

  'Retailer': retailerStyle,
  'AllRetailers': retailerStyle,

  'Wholesaler': wholeSalerStyle,
  'AllWholesalers': wholeSalerStyle,

  'FrameSupplier': { color: "#6FC1A7", icon: "\uEA05" },
  'WheelSupplier': { color: "#6FC1A7", icon: "\uEA08" },

  'Product': { color: "#76378A", icon: "\uEA02" }
};

4. Adding styles

Edges color and size coding:

// edge color and width buckets
const edgeBuckets = ogma.rules.slices({
  field: "neo4jProperties.quantity",
  values: [
    { color: "#DDD", width: 0.5 },
    { color: "#AAA", width: 1.5 },
    { color: "#777", width: 2 },
    { color: "#444", width: 3.5 }
  ],
  // buckets are
  // (-Infinity, 15], (15, 150], (150, 280], [280, Infinity)
  stops: [15, 150, 280]
});

4. Adding styles

Apply edge styling

ogma.styles.addRule({
  edgeAttributes: (edge) => {
    const quantity = edge.getData("neo4jProperties.quantity");
    const { color, width } = edgeBuckets(edge);
    return {
      shape: { head: "arrow" },
      color, width
    };
  }});

4. Adding styles

Apply edge styling

4. Adding styles

Apply node styling

ogma.styles.addRule({
  nodeAttributes: (node) => {
    // get data field
    const type = getNodeType(node);
    // match with the styles object
    const { color, icon } = nodeStyles[type];

    // sizing: quantity of incoming edges / 300
    // nodes size depend on the quantity of Product that is exanged through them
    const radius = node
      .getAdjacentEdges()
      .reduce((acc, e) => acc + e.getData("neo4jProperties.quantity"), 0) / 300 + 5;

    return {
      icon: {
        font: "icon-font",
        color: "#000000",
        content: icon,
      },
      color, radius
    };
}});

4. Adding styles

Apply node styling

4. Adding styles

Add insights

// Helper function to check if a node should pulse
// Nodes pulses if the total demand is higher than their stock
function hasRisk(node) {
  const totalDemand = node
    .getAdjacentEdges()
    .filter((edge) => edge.getSource() === node)
    .reduce((total, edge) => total + edge.getData("neo4jProperties.quantity"), 0);
  return node.getData("neo4jProperties.stock") - totalDemand < 100;
}

// enable pulse animation
ogma.getNodes().filter(hasRisk).pulse();

4. Adding styles

Add insights

5. Geo mode + Transformations

Let's look at the the same graph with the geo reference

// let Ogma know where to look for the coordinates
ogma.geo.enable({
  longitudePath: 'neo4jProperties.lon',
  latitudePath: 'neo4jProperties.lat'
});

5. Geo mode + Transformations

Let's clear up the view a bit

First let's group the parallel edges

const edgeGrouping = ogma.transformations.addEdgeGrouping({
  selector: (edge) => edge.getData("neo4jType") === "DELIVER",
  generator: (edges) => {
    return {
      data: { neo4jProperties: {  quantity: edges.reduce(...), 0), }}
    };
  }
});

5. Geo mode + Transformations

Parallel edges are grouped

5. Geo mode + Transformations

Let's group the nodes: join the raw suppliers, wholesalers and group the part suppliers:

const nodeGrouping = ogma.transformations.addNodeGrouping({
  groupIdFunction: (node) => node.getData("neo4jLabels")[0],
  // don't group the product
  selector: (node) => node.getData("neo4jLabels")[0] !== "Product",
  nodeGenerator: (nodes, groupId) => {
    // choose label for the style
    let label = ['Wholesalers']; // default
    if (groupId === "SupplierA") label = ["FrameSupplier"];
    else if (groupId === "SupplierB") label = ["WheelSupplier"];
    else if (/RawSupplier/.test(groupId)) label = ["RawSuppliers"];
    else if (groupId === 'Retailer') label = ["Retailers"];

    // sizing criteria
    const quantity = nodes.getData("neo4jProperties.quantity")
      .reduce((total, q) => total + q, 0);

    // generated group node
    return {
      data: {
        neo4jLabels: label,
        neo4jProperties: { quantity }
      }
    };
  }
});

5. Geo mode + Transformations

Nodes are now grouped by the type:

5. Geo mode + Transformations

Let's make sense of this new view on the supply chain by applying a different layout:

6. UI & frameworks

Let's add some controls

            
            // index.js
const edgeGroupingButton = document.getElementById('edge-grouping');
edgeGroupingButton.addEventListener('click', () => {
  edgeGrouping.toggle()
    .then(() => {
      const state = edgeGrouping.isEnabled() ? 'on' : 'off';
      edgeGroupingButton.innerHTML = 'Edge grouping: ' + state;
    });
});
            
          

6. UI & frameworks

6. UI & frameworks

Final result

6. UI & frameworks

Ogma integrates nicely with all the popular front-end frameworks

Questions?

ogma@linkurio.us

Ogma docs

Distribution center