All versions of this documentation
X

Getting started

What is Ogma?

Ogma is the JavaScript graph visualization library created by Linkurious. It provides a powerful graphics engine based on WebGL, and can support old machines with HTML5 Canvas.

It provides all features needed to display, explore, and work with graph data within Web applications such as layout algorithms, imports and exports from/to various formats, rich user interaction, or custom design.

Basic concepts of graphs

A graph is a set of entities connected to each others by relationships. In Ogma, entities are called "nodes" and relationships are called "edges".

Key concepts of Ogma

Ogma is made of modules.

Events

Modules communicate by emitting and catching events.

Settings

  • Most modules can be configurable with settings.
  • These settings allow to change the behavior of the module without custom code.
  • All settings have a default value: Ogma will work even if no setting is specified at initialization.

A typical example of setting would be the color of edges when they are hovered by the cursor.

Core modules vs plugins

There are two kinds of modules: core modules and plugins. Core modules are integrated by default to every instance of Ogma, whereas plugins must be explicitly specified at initialization.

Renderers

Ogma can display the graph using three different technologies:

  • WebGL: most powerful ;
  • Canvas: HTML 5 canvas, much slower than WebGL but widely supported ;
  • SVG

By default, Ogma will try to use WebGL, and fallback on Canvas if it is not available. It is possible to choose manually the technology to use.

Compatibility

Ogma can run on computers and tablets, and is compatible with the following browsers:

  • Firefox >= 17
  • Chrome >= 14
  • Safari >= 6
  • Internet Explorer >= 10
  • Edge >= 13

It can also be run in Node.js >= 4.4.0.

Installing the library

Add the following line to your Web page:

<script src="path/to/ogma.min.js"></script>

If you want to include all optional dependencies and Ogma at once:

<script src="path/to/ogma.dist.min.js"></script>

In Node.js:

var Ogma = require('path/to/ogma.min.js');

Hello World!

Let's make a minimalist example: we will display a graph with two nodes and one edge. We can add graph data to Ogma in two ways: 1) initializing the library then adding the nodes and edge, or 2) adding the nodes and edge at initialization.

Add nodes and edges dynamically

<!-- Include the library -->
<script src="../build/ogma.min.js"></script>

<!-- This div is the DOM element containing the graph. The style ensures that it takes the whole screen. -->
<div id="graph-container" style="position: absolute; left: 0; top: 0; bottom: 0; right: 0;"></div>

<script>
  var ogma = new Ogma({
    // Indicates the DOM element in which the graph must be displayed.
    // It will be looked up with document.getElementById()
    container: 'graph-container'
  });

  ogma.graph.addNodes([
    // Add a node and specify its shape, color, position and size:
    {id: 'n1', color: 'red', shape: 'square', x: 0, y: 0, size: 3, text: 'Node 1'},
    // Add a node without specifying a shape nor color:
    // it will be given the default color and default shape
    {id: 'n2', x: 30, y: 10, size: 3, text: 'Node 2'},
    {id: 'n3', x: 10, y: 30, size: 2, shape: 'cross', color: 'blue', text: 'Node 3'}
  ]);
  ogma.graph.addEdges([
    // Add an edge between two nodes:
    {id: 'e1', source: 'n1', target: 'n2', size: 1},
    {id: 'e2', source: 'n2', target: 'n3', size: 1},
    {id: 'e3', source: 'n3', target: 'n1', size: 1}
  ]);
</script>

Initialize with a graph object

Replace the content of the <script> tag by this:

var ogma = new Ogma({
    container: 'graph-container',
    graph: {
      nodes: [
        {id: 'n1', color: 'red', shape: 'square', x: 0, y: 0, size: 3, text: 'Node 1'},
        {id: 'n2', x: 30, y: 10, size: 3, text: 'Node 2'},
        {id: 'n3', x: 10, y: 30, size: 2, shape: 'cross', color: 'blue', text: 'Node 3'}
      ],
      edges: [
        {id: 'e1', source: 'n1', target: 'n2', size: 1},
        {id: 'e2', source: 'n2', target: 'n3', size: 1},
        {id: 'e3', source: 'n3', target: 'n1', size: 1}
      ]
    }
  });

Result:

Hello world

You can also set the container property later:

var ogma = new Ogma();

ogma.render.setContainer('graph-container');

Settings

Most of Ogma's modules are configurable through settings. Here we keep the same example but this time we want to display node texts on the left instead of the bottom, and we want nodes to be green by default:

var ogma = new Ogma({
  container: 'graph-container',
  graph: {
    nodes: [
      {id: 'n1', color: 'red', shape: 'square', x: 0, y: 0, size: 3, text: 'Node 1'},
      {id: 'n2', x: 30, y: 10, size: 3, text: 'Node 2'},
      {id: 'n3', x: 10, y: 30, size: 2, shape: 'cross', color: 'blue', text: 'Node 3'}
    ],
    edges: [
      {id: 'e1', source: 'n1', target: 'n2', size: 1},
      {id: 'e2', source: 'n2', target: 'n3', size: 1},
      {id: 'e3', source: 'n3', target: 'n1', size: 1}
    ]
  },
  settings: {
    texts: {
      nodeTextAlignment: 'left'
    },
    shapes: {
      defaultNodeColor: 'green'
    }
  }
});

The settings parameter is an object for which each key is the name of a module, and the value is the setting.

Settings

It is also possible to change settings after the initialization:

ogma.settings.update('shapes', {
  defaultNodeColor: 'indigo'
});

For now, let's reset both settings to their default value with the reset method:

ogma.settings.reset('texts');   // Resets all settings of the module 'texts' to their default value
ogma.settings.reset('shapes');  // Resets all settings of the module 'shapes' to their default value

Import data

Now let's work with real-world data. We need to load existing data stored in an external file. Ogma can read two file formats: GEXF, an XML dialect supported by Gephi, and JSON.

The current graph is cleared before the new graph is imported from a file. Let's load a JSON file available here:

ogma.imports.from('json', 'files/solarCity-nostyle.json').then(function () {
  console.log('File successfully imported!');
}).catch(function (error) {
  console.log('Import failed: ' + error);
});
  • The first argument is the format of the file to parse.
  • The second argument is the URL of the file to parse.
  • This method returns a Promise
  • If you don't want to use Promises, a third argument can be specified, a callback that takes a boolean (success or not)

Result:

Import

Important note

The import is asynchronous: if we want to run code after the graph is loaded, we must put it inside the Promise handler or the callback.

Neo4J connector

Additionally, Ogma provides a Neo4J Bolt connector to load a graph from a Neo4J 3.0+ database. Example:

var ogma = new Ogma({
  container: 'graph-container',
  plugins: ['neo4j'] // Don't forget to include the plugin!
});

ogma.neo4j.connect('localhost:7687', 'test', 'test'); // address, username, password

ogma.neo4j.query(
  'MATCH (n) OPTIONAL MATCH (n)-[r]-(m) RETURN n,r,m LIMIT 100'
).then(function (result) {
  // `result` contains the result of the request, if we want to do additional work
  console.log('Import done.');
});

Update a graph dynamically

Let's add some extra nodes and edges to the graph. We will add a node and connect it to the second node of the graph (reminder: if you have just imported a graph from a file, you must put this code in the callback of the imports.from method):

var nodes = ogma.graph.nodes;

ogma.graph.addNode({id: 'myNode', size: 3, shape: 'square', color: 'blue', x: 25, y: 45});
ogma.graph.addEdge({id: 'myEdge', source: 'myNode', target: nodes[1].id});

Dynamic updates

We change the color of every node in the graph:

for (var i = 0; i < nodes.length; ++i) {
  nodes[i].color = 'crimson';
}

Dynamic updates

Camera

The camera has three components: position, zoom, angle. Here is a short overview of the API:

// Move the camera of 200 units towards the right
ogma.camera.move(200, 100);
// Double the level of zoom over 400 ms
ogma.camera.zoomIn({zoomModifier: 2, duration: 400});
// Rotate the camera by 90 degrees over 1 second
ogma.camera.rotate(Math.PI / 2, {duration: 1000});

Additionally, the locate module provides higher-level methods to manipulate the camera, such as centering the camera on the graph:

ogma.locate.center();

Changing the text of nodes

We want to change the text of every node based on their data.properties.name property. We can do it either by 1) changing the text property of all nodes, or 2) mapping the property to the node text using the design module.

Set node texts

ogma.graph.nodes.forEach(function (node) {
  node.text = node.data && node.data.properties && node.data.properties.name;
});

If data, data.properties or data.properties.name does not exist, the text will be assigned to undefined (no text).

Map associated data to node texts

We can also use the design module to change visual attributes of nodes and edges. This module immediately updates modified nodes and edges or those that are added to the graph, and provides the ability to revert changes.

Here is the same example by using the design module:

ogma.design.setNodeText(function (node) {
  return node.data && node.data.properties && node.data.properties.name;
});

Result:

Change text

We can easily revert the texts to their original value:

ogma.design.resetNodeText();

Notice that for the nodes that use the data property to define their text (or any other design variable), the user must call ogma.graph.notifyNodeDataChange(nodes) when the data property changes for the node text to be updated.

Rank nodes with colors and size

Nodes and edges may have associated data but no visual attribute defined such as color, size, or shape.

As we have seen previously, the design module enables us to map these visual attribute based on the data associated to the nodes and edges.

Let's map the size of nodes according to their data.properties.funding_total property:

ogma.design.setNodeSize({
  variable: 'data.properties.funding_total',
  min: 4,
  max: 10,
  bins: 7
});
  • The first argument is the visual attribute we want to map.
  • The second argument provide parameters:
    • variable: data from which the value must be taken.
    • min, max: minimum and maximum size: the nodes with the highest data.properties.funding_total value will be given a size of 10, and the nodes with the lowest value will be given a size of 4. Size of other nodes are linearly mapped between 4 and 10.
    • bins: number of different sizes that can be given. Here, a node can be assigned a size of 4, 5, 6, 7, 8, 9 or 10.

We can also map the color of the nodes to the same property:

ogma.design.setNodeColor({
  variable: 'data.properties.funding_total',
  scheme: ['#161344','#3f1c4c','#632654','#86315b','#a93c63','#cd476a','#f35371']
});

In the same way, the nodes with the lowest value will be given the color #161344 (the first one in the list) and the node with the highest value will be given the color #f35371 (the last one in the list). We could revert the order with further options.

Result:

Rank

Notice how the node (hint: square shape) that we have manually added is not affected, since it doesn't have a data.properties.funding_total property.

Notice that for the nodes that use the data property to define any visual property through the design module, the user must call ogma.graph.notifyNodeDataChange(nodes) when the data property changes for the node visual property to be updated.

Show node categories with colors and shapes

We have seen how to map the color or the size of nodes to quantitative properties (properties with numerical values). Let's see how to map visual attributes to qualitative properties (properties that represent categories, values can be of any type).

For example, this code assigns the color of nodes depending on their categories (it overrides existing color style):

ogma.design.setNodeColor({
  variable: 'data.categories',
  scheme: {
    'COMPANY': '#ff461f',
    'INVESTOR': '#4e81ff',
    'MARKET': '#559348'
  }
});

The scheme parameter maps the data to the visual attribute. If a value is missing, the visual attribute will be assigned to undefined (default value for that attribute defined by the corresponding setting).

The node will be split into several colors like a pie chart if the property is an array (i.e the node has multiple categories). The first value of the array is picked for other visual attributes (shape, icon, image).

Result:

Categories 1

Once again, notice how the node that we have manually added is not affected, since it doesn't have a data.categories property.

Alternatively, we can map the shape of nodes to the node categories:

ogma.design.setNodeShape({
  variable: 'data.categories',
  scheme: {
    'COMPANY': 'cross',
    'INVESTOR': 'star',
    'MARKET': 'diamond'
  }
});

Result:

Categories 1

Notice that for the nodes that use the data property to define any visual property through the design module, the user must call ogma.graph.notifyNodeDataChange(nodes) when the data property changes for the visual property to be updated.

Layouts

Layout algorithms position the nodes of the graph to reveal structural patterns. For example, the ForceLink layout makes so that connected nodes attract each others, and disconnected nodes repulse each others.

This example runs the ForceLink layout with a nodeSiblingsScale factor of 8, and animate the positioning of nodes over 300ms, then center the camera with an animation over 300ms.

ogma.layouts.start('forceLink', {
  nodeSiblingsScale: 8
}, {
  duration: 300, 
  onEnd: function () {
    // Center the camera on the graph over 300 ms
    ogma.locate.center({duration: 300});
  }}
).then(function () {
  console.log('Layout completed!');
});
  • The first argument is the name of the layout.
  • The second argument is the parameters of the layout.
  • The third argument are parameters common to all layouts:
    • duration: duration of the movement animation
    • onEnd: function called once the layout is finished
  • The method returns a Promise. You can choose to use either the Promise or the callback.

We can find the list of available layouts and their parameter in the Layouts module.

Important note

The layout is asynchronous, so if you want to run code after the layout is done, you must put it inside the onEnd callback function, or inside the Promise handler.

Layout

Filter nodes

The following example shows all nodes that have a data.properties.funding_total property greater or equal to 300 million, and hide other nodes.

ogma.filter.showNodes(['data.properties.funding_total', '>=', {value: 300000000}]);

Nodes which don't have the data.properties.funding_total property are ignored (their visibility is not affected).

The argument uses the Ogma DSL syntax (see the DSL module), which allows a compact syntax, but we can also use a function. The following code gives the same result:

ogma.filter.showNodes(function (node) {
  if (!node.data || !node.data.properties || node.data.properties.funding_total === undefined) {
    return undefined; // so the node is ignored
  } else {
    return node.data.properties.funding_total >= 300000000;
  }
});

Result:

Filter

Notice that some nodes are not affected by the filter: for example, the node that we have manually added is not hidden because it doesn't contain any data.

Export to file

We have customized the appearance of the graph, and we want to export it to add it to a report.

We can export a PNG file:

ogma.exports.to('png', {filename: 'myCustomizedGraph.png'});

We also need to archive the data, so we export the graph to a GEXF file:

ogma.exports.to('gexf', {filename: 'myCustomizedGraph.gexf'});

This method returns a Promise, so you can do:

ogma.exports.to('gexf', {filename: 'myCustomizedGraph.gexf'}).then(function () {
  console.log('Export done!);
});

The full list of export formats and their parameters are available in the exports module.

Listen to mouse events

Interaction feature are usually designed for the mouse or the trackpad. Here is an example on how to display the id of the node or edge under the cursor when the user clicks or taps on it:

ogma.events.bind({
  'captor.clickNode': function (node, x, y) {
    console.log('click on node ' + node.id + ' at', x, y);
  },

  'captor.clickEdge': function (edge, x, y) {
    console.log('click on edge ' + edge.id + ' at', x, y);
  }
});

Notes on performance

Here are some general pieces of advice on performance:

  • Use one graph.addNodes() instead of multiple graph.addNode() (same for edges), as well as graph.removeNodes() isntead of multiple graph.removeNode().
  • Text is really expensive in term of graphics memory. For reference, one character uses as much space as a node shape. Consider disabling texts if you run out of graphics memory.
  • When working with very large graphs, consider disabling anti-aliasing at all by setting the render::webGLAntiAliasing setting to 'none'. It will make the graph look uglier, but it should significantly increase performance.
  • Big elements on screen takes an order of magnitude longer to be displayed: adjustments such as decreasing the size of nodes, or zooming-out can make the interactions much smoother.
  • Animating elements with the drawing module is almost free.

Contact