All versions of this documentation
X

Styling API Download code

Nodes and edges have multiple visual attributes: position, color, shape, size, image, text, etc (see the full list here). Ogma provides multiple ways to alter these attributes, depending on the need.

Most sections of this tutorial will be illustrated with examples using nodes, but it works exactly the same for edges.

Basics

Assigning attributes

The most simple way to change the attributes of an element (node or edge) is to use the setAttributes method:

var node = ogma.addNode({id: 0});

node.setAttributes({x: 10, y: 30, color: 'blue', text: {content: 'My node'}});

It is also possible to specify attributes when adding a node/edge, by providing an attributes property. Attributes specified this way are called "original attributes".

ogma.addNode({id: 1, attributes: {x: 30, y: 30, color: 'red', text: {content: 'My other node'}}});

Result:

Note: in case of nested attributes, you can often use a shortcut to specify the main "sub-attribute":

// The following two lines are equivalent:

node.setAttributes({text: {content: 'Some text'}});

node.setAttributes({text: 'Some text'});
// Here the value of the `text` property is not an object, thus is alias-ed to `text.content`

Alternatively, you can set a single attribute with setAttribute (with no 's'):

node.setAttribute('shape', 'square');
node.setAttribute('text.color', 'blue');

// Notice how you specify a nested property name

Retrieving attributes

You can retrieve a single attribute with getAttribute (without the 's'):

var node = ogma.addNode({id: 0});
node.setAttributes({x: 10, y: 30, color: 'blue', text: {content: 'My node'}});

console.log(node.getAttribute('color')); // "blue"

To retrieve some (or all) attributes of a node, use the getAttributes method:

var node = ogma.addNode({id: 0});
node.setAttributes({x: 10, y: 30, color: 'blue', text: {content: 'My node'}});

console.log(node.getAttributes(['x', 'y', 'text.content']));
// {x: 10, y: 30, text: {content: 'My node'}}
  • The method takes the list of attributes to retrieve.
  • If no argument is specified, the method will return an object containing all attributes.
  • Nested attributes are specified with a dot-separated string.

Note: There is also a node.getPosition() method that is a shortcut for node.getAttributes(['x', 'y']).

Resetting attributes

You can reset attributes to their original value (the ones specified when the node/edge was added) by using the resetAttributes method:

var node = ogma.addNode({id: 2, attributes: {x: 20, y: 50, color: 'green', text: {content: 'Yet another node'}}});

node.setAttributes({color: 'darkblue', shape: 'square'});

console.log(node.getAttribute('color')); // "darkblue"
console.log(node.getAttribute('shape')); // "square"

node.resetAttributes();

console.log(node.getAttribute('color')); // "green"
console.log(node.getAttribute('shape')); // "circle"  no original
// attribute was specified for the "shape" attribute, so the system
// default value is used

Before reset:

After reset:

By default, all attributes are reset but it is possible to specify a set of attributes to reset:

// Reset the coordinates and the color back to their original value.
// All other attributes are left untouched
node.resetAttributes(['x', 'y', 'color']);

Working with NodeList/EdgeList

All these methods also work on the NodeList/EdgeList data structures:

// Color all the nodes in the graph in gold
ogma.getNodes().setAttributes({color: 'gold'});

When used with a NodeList/EdgeList object, the setAttributes method can also take a list of attributes objects as parameter. In that case, the first object is assigned to the first element of the list, the second object to the second element, etc.

ogma.getNodes(['My node', 'Yet another node']).setAttributes([
  {x: 0, y: 40, radius: 3, color: 'lightgreen'},
  {x: 5, y: 55, radius: 2, color: 'pink'},
]);

console.log(ogma.getNode('My node').getAttribute('color')); // "lightgreen"
console.log(ogma.getNode('Yet another node').getAttribute('color')); // "pink"

Similarly, calling getAttributes on a NodeList or EdgeList retrieves a list of objects, and getAttribute retrieves a list of values:

var nodes = ogma.getNodes(['My node', 'Yet another node']);

nodes.setAttributes([
  {x: 0, y: 40, radius: 3, color: 'lightgreen'},
  {x: 5, y: 55, radius: 2, color: 'pink'},
]);

console.log(nodes.getAttributes(['x', 'y'])); // [{x: 0, y: 0}, {x: 10, y: 20}]
console.log(nodes.getAttribute('color')); // ['red', 'blue']

A very straightforward usage of this feature is the ability to save the state of a list of nodes, and restore it later.

The resetAttributes method also work with lists:

// Reset the graph to its original state
ogma.getNodes().resetAttributes();
ogma.getEdges().resetAttributes();

Animations

If desired, attribute transitions can be animated. Specify the duration (in milliseconds) as the second parameter of setAttributes.

var node = ogma.addNode({id: 3, attributes: {color: 'red'}});

// Animate the color change of the node from red to blue during 500 ms.
node.setAttributes({color: 'blue'}, 500);

Global rules

Creating rules

Most often, graphs don't contain any attribute, but contain node and edge data. When loaded in Ogma, they look raw because the default style is applied.

ogma.setGraph({
  nodes: [
    {id: 0, data: {type: 'country', name: 'USA', population: 325365189, currency: 'USD'}, attributes: {x: 0.1414247453212738, y: 36.075706481933594}},
    {id: 1, data: {type: 'country', name: 'France', population: 66991000, currency: 'EUR'}, attributes: {x: -13.736449241638184, y: -31.714202880859375}},
    {id: 2, data: {type: 'country', name: 'Germany', population: 82175700, currency: 'EUR'}, attributes: {x: 26.934364318847656, y: -31.93191909790039}},
    {id: 3, data: {type: 'person', name: 'Norwood Preston', age: 44}, attributes: {x: 6.068506240844727, y: 6.21354866027832}},
    {id: 4, data: {type: 'person', name: 'Kurtis Levi', age: 24}, attributes: {x: 29.330284118652344, y: 22.172880172729492}},
    {id: 5, data: {type: 'person', name: 'Amias Kev', age: 38}, attributes: {x: -24.19040298461914, y: 35.77732849121094}},
    {id: 6, data: {type: 'person', name: 'Carver Derren', age: 16}, attributes: {x: -34.62548065185547, y: -22.868541717529297}},
    {id: 7, data: {type: 'person', name: 'Bevis Tel', age: 73}, attributes: {x: -22.90767478942871, y: -4.446183681488037}},
    {id: 8, data: {type: 'person', name: 'Loyd Garfield', age: 32}, attributes: {x: 32.98543167114258, y: -9.278616905212402}},
  ],
  edges: [
    {id: 0, source: 3, target: 0, data: {type: "lives_in"}},
    {id: 1, source: 4, target: 0, data: {type: "lives_in"}},
    {id: 2, source: 5, target: 0, data: {type: "lives_in"}},
    {id: 3, source: 6, target: 1, data: {type: "lives_in"}},
    {id: 4, source: 7, target: 1, data: {type: "lives_in"}},
    {id: 5, source: 8, target: 2, data: {type: "lives_in"}},
    {id: 6, source: 3, target: 4, data: {type: "knows"}},
    {id: 7, source: 3, target: 8, data: {type: "knows"}},
    {id: 8, source: 3, target: 7, data: {type: "knows"}},
    {id: 9, source: 4, target: 8, data: {type: "knows"}},
    {id: 10, source: 6, target: 7, data: {type: "knows"}},
  ]
});

Rather than looping through every node, we can define global rules to assign the attributes of the nodes and edges based on their data:

ogma.styles.addNodeRule({
  text: function (node) {
    return node.getData('name');
  },
  color: function (node) {
    return node.getData('type') === 'country' ? 'green' : 'orange';
  },
  radius: function (node) {
    if (node.getData('type') === 'country') {
      return 5 + node.getData('population') / 100000000;
    } else {
      return 3;
    }
  }
});

ogma.styles.addEdgeRule({
  shape: function (edge) {
    return edge.getData('type') === 'lives_in' ? 'arrow' : 'line';
  },
  text: function (edge) {
    return edge.getData('type');
  }
});

A rule is an object similar to the NodeAttributes structure, except that the values can be functions instead of constants. In that case, functions are evaluated each time the rule is re-computed (e.g. when the data changes).

Quick note on data: the data property is an object which can be anything. It is designed to carry any kind of information on the node/edge, and is manipulable through methods getData and setData. See the next tutorial for a more in depth explanation.

Advantages of rules instead of setAttributes:

  • Rules automatically apply to nodes/edges added to the graph.
ogma.styles.addNodeRule({color: 'red'});
var node = ogma.addNode({id: 0});

console.log(node.getAttribute('color')); // "red"
  • Rules automatically re-compute whenever the data of nodes/edges is updated.
ogma.styles.addNodeRule({
    text: function (node) {
      return node.getData('name');
    }
});

var node = ogma.addNode({id: 0, data: {name: 'John'}});

console.log(node.getAttribute('text')); // "John"

node.setData('name', 'James');

// We use `setTimeout` because the data update is asynchronous
setTimeout(function () {
  console.log(node.getAttribute('text')); // "James"
}, 0);
  • Rules have higher priority than original attributes, but less than attributes set via setAttributes. This means that you can build a logic that uses setAttributes on top of the global rules:
var node = ogma.addNode({id: 0, attributes: {color: 'blue'}, data: {name: 'Google', type: 'company'}});

node.setAttributes({text: 'Facebook'});

ogma.styles.addNodeRule({
    color: function (node) {
      if (node.getData('type') === 'company') {
        return 'red';
      } else {
        return 'black';
      }
    },
    text: function (node) {
      return node.getData('name');
    }
});

// The text set by `setAttributes` takes precedence over the rule
console.log(node.getAttribute('text')); // "Facebook"

// The color assigned by the rule takes precedence over the original color
console.log(node.getAttribute('color')); // "red"

Note: addNodeRule returns an object that can be used to manipulate the rule (removing, refreshing or re-ordering it)

Removing rules

Removing a rule is done with the destroy method:

var node = ogma.addNode({id: 0, attributes: {color: 'blue'}});
var rule = ogma.styles.addNodeRule({color: 'red'});

rule.whenApplied(function () {
  console.log(node.getAttribute('color')); // "red"
}).then(function () {
  return rule.destroy();
}).then(function () {
  console.log(node.getAttribute('color')); // "blue"
});

Re-ordering rules

Multiple rules can be applied at the same time. Therefore, conflicts may occur:

// Simple case: what color is assigned to the nodes?

ogma.styles.addNodeRule({color: 'red'});
ogma.styles.addNodeRule({color: 'blue'});

Rules are internally stored in an array: every time a rule is added, it is added at the beginning of the array. In case of conflict, the rule that appear first in the array (i.e the last that has been added) takes precedence.

Although not common, it may be necessary to re-order the rules within this array in order to modify their priority. In such case, the following methods are useful:

  • rule.getIndex(): retrieves the index of the specified rule
  • rule.setIndex(index): changes the index of the specified rule

Here is an example that demonstrates swapping the priority of two rules:

function swapRulePriority(rule1, rule2) {
  var index1 = rule1.getIndex();
  var index2 = rule2.getIndex();

  rule1.setIndex(index2);
  rule2.setIndex(index1);
}

Refreshing rules

It can happen that your rule references an external variable. If this variable changes, you need to manually refresh the rule to take the change into account:

var myMapping = {
  person: 'red',
  country: 'blue',
  company: 'green'
};

var rule = ogma.styles.addNodeRule({
  color: function (node) {
    return myMapping[node.getData('type')];
  }
});

// Change a variable referenced by the rule
myMapping.company = 'purple';

// Manually refresh the rule
rule.refresh();

Retrieving all rules

You can retrieve all the node rules with getNodeRules():

// This example deletes all the node rules

ogma.styles.getNodeRules().forEach(function (rule) {
  rule.destroy();
});

Classes

Classes are a way to highlight nodes/edges to show they are in a meaningful and temporary state. Ogma has two builtin classes: "hovered" and "selected", and users can add custom classes.

Selection and hover

These two classes are managed by Ogma itself. To affect how the nodes look like when hovered/selected, Ogma provides the setHoveredNodeAttributes and setSelectedNodeAttributes methods:

var node = ogma.addNode({id: 0});

ogma.styles.setSelectedNodeAttributes({outerStroke: {color: 'green'}});
node.setSelected(true);

console.log(node.getAttribute('outerStroke.color')); // "green"

Note that functions can also be passed as value for the attributes:

// In this example, we display the name of the nodes as their text, only when they are selected

var node = ogma.addNode({id: 0, data: {name: 'John'}});

ogma.styles.setSelectedNodeAttributes({
  outerStroke: {
    color: 'green'
  },
  text: function (node) {
    return node.getData('name');
  }
});

console.log(node.getAttribute('text')); // null
node.setSelected(true);
console.log(node.getAttribute('text')); // "John"

You can also pass null so the attributes of nodes are not changed when hovered/selected:

ogma.styles.setSelectedNodeAttributes(null);

Creating custom classes

  • Creating a class is done via the ogma.createClass method.
  • Adding and removing a class from a node/edge is done via the addClass and removeClass methods.
  • It is possible to retrieve all the nodes that have a specific class with ogma.getNodesByClassName.

In this example, we highlight the shortest path between a pre-determined node and the node being right-clicked:

// Define the class, and the attributes associated to it
ogma.createClass('shortestPathHighlight', {
  nodeAttributes: { outerStroke: { color: 'black', width: 10 } },
  edgeAttributes: { strokeWidth: 5, color: 'black' }
});

ogma.events.onClick(function (evt) {
  if (evt.button !== 'right') return;

  // This is the node used to compute the shortest path
  var sourceNode = ogma.getNode(0);

  // When the user right-clicks, first clear the current highlighted shortest path, if any
  ogma.getNodesByClassName('shortestPathHighlight').removeClass('shortestPathHighlight');
  ogma.getEdgesByClassName('shortestPathHighlight').removeClass('shortestPathHighlight');

  // If a node was right-clicked, highlight the shortest path
  if (evt.target && evt.target.isNode) {
    var shortestPath = ogma.pathfinding.dijkstra(sourceNode, evt.target);

    // `shortestPath` is null if no path exists
    if (shortestPath) {
      // Highlight all the nodes in the shortest path
      shortestPath.addClass('shortestPathHighlight');

      // Get all the edges that connect the nodes of the shortest path together, and highlight them
      shortestPath.getAdjacentEdges().filter(function (edge) {
        return shortestPath.includes(edge.getSource()) && shortestPath.includes(edge.getTarget());
      }).addClass('shortestPathHighlight');
    }
  }
});

// Finally, load a graph on which to test the feature
ogma.generate.grid({rows: 5, columns: 5}).then(function (graph) {
  return ogma.setGraph(graph);
}).then(function () {
  // Remove a few edges to make things more interesting
  return ogma.removeEdges([0, 2, 3, 5, 7, 10, 13, 14, 18, 19, 22]);
}).then(function () {
  return ogma.view.locateGraph();
});

A few things to note:

  • Class attributes takes precedence over individual attributes and rule attributes.
  • In case of conflicting classes, the one that has been created first takes precedence.