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`

You can also 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 assigned

Before reset:

After reset:

By default, all attributes are reset but it is possible to specify a subset 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) and easing 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'}, {duration: 500, easing: 'quadraticOut'});

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.addRule({
  nodeAttributes: {
    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;
      }
    }
  },
  edgeAttributes: {
    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 whose content is up to the user. It is manipulable through methods getData and setData. See the next tutorial for a more in depth explanation.

Selectors

It is possible to specify a selector to indicate on which nodes/edges the rule should be applied:

ogma.styles.addRule({
  nodeAttributes: {color: 'red'},

  // The rule will only be applied to nodes for which the data `foo` is equal to `'bar'`
  nodeSelector: function(node) {
    return node.getData('foo') === 'bar';
  }
});

Advantages of rules instead of setAttributes:

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

console.log(node.getAttribute('color')); // "red"
  • Rules are automatically re-applied whenever the data of nodes/edges is updated.
ogma.styles.addRule({
  nodeAttributes: {
    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.addRule({
  nodeAttributes: {
    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.addRule({nodeAttributes: {color: 'red'}});

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

rule.destroy();

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.addRule({nodeAttributes: {color: 'red'}});
ogma.styles.addRule({nodeAttributes: {color: 'blue'}});

Rules are internally stored in an array: every time a rule is added, it is added at the end of the array. When attributes are computed, rules are applied one by one in the order they appear in the array (by default rules added last are applied last).

Although not common, it may be necessary to re-order the rules within this array in order to modify the order in which they are applied. 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 a rule references an external variable. If this variable changes, the rule need to be manually refreshed to take the change into account:

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

var rule = ogma.styles.addRule({
  nodeAttributes: {
    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 rules with styles.getRuleList():

// This example deletes all the rules

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

Classes

Classes are a way to highlight nodes/edges to show they are in a meaningful and temporary state.

Selection and hover

These two classes are managed by Ogma itself, and are not directly accessible by the user. 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.styles.createClass method.
  • Adding and removing a class from a node/edge is done via the addClass and removeClass methods.
  • It is possible to do operations on the class object directly (such as retrieving the list of nodes/edges that have the class)

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
var myClass = 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
  myClass.clearNodes();
  myClass.clearEdges();

  // 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.
  • Classes are applied using the same mechanism as rules: the last added class is applied last
  • Builtin classes (such as hover and selection) are always applied last, after user classes

Final overview

Attributes are computed in the following order:

  • original attributes
  • rules (from first added to last added)
  • individual attributes (that are assigned with setAttributes)
  • user classes (from first added to last added)
  • builtin classes (hover & selection)

It is especially important to keep this order in mind when defining rules/classes that are computed using other attributes.

Performance notes

When working with a large number or nodes/edges:

  • avoid using functions in rules/classes; use constants as much as possible
  • prefer using one single method on a NodeList/EdgeList rather that the same method on each node/edge individually (e.g nodeList.setAttributes({color: 'red'}) rather than nodeList.forEach(node => node.setAttributes({color: 'red'}))).