Skip to content
  1. Tutorials

Using data API

In addition to their attributes, which is a fixed data-structure, nodes and edges also have a data property. This property is used to store information about the node/edge that are relevant in the context of the application. For example, the type of entity a node represents (person? company?), a name, a social security number, a number of employees, an ip address, etc.

There are three main usages of data:

  • filtering nodes/edges out of the graph
  • styling the graph according to the content of the data (e.g companies in green, people in red)
  • keeping track of information about some given properties (e.g the number of nodes for which the property foo is equal to "bar")

Basics

Initializing data

It is possible to specify some data when the node/edge is added:

js
const node = ogma.addNode({ id: 0, data: { foo: 'bar' } });

node.getData('foo'); // "bar"
node.getData('foo2'); // undefined

If no data property is specified, an empty object is assigned.

Accessing data

Accessing a data property is done through the methods getData and setData:

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

node.getData('foo'); // undefined
node.setData('foo', 'bar');
node.getData('foo'); // "bar"

These methods also work on lists:

js
ogma.addNodes([{ id: 0 }, { id: 1 }]).then(function (nodes) {
  // The first element of the array is assigned to the first node of the list,
  // the second element to the second node, etc
  nodes.setData('foo', ['value1', 'value2']);

  nodes.getData('foo'); // ["value1", "value2"]
});

Contrary to setAttributes, setData does not allow to assign the same value to all nodes/edges of a list: an array could be treated as multiple values to be spread across the list or as one value to be assigned to all nodes/edges, without any way for Ogma to know which to pick.

To solve this problem, a third method is available on NodeList/EdgeList, fillData:

js
ogma.addNodes([{ id: 0 }, { id: 1 }]).then(function (nodes) {
  nodes.fillData('foo', 'bar');
  nodes.getData('foo'); // ["bar", "bar"]
});

For these three methods, the name of the property can be omitted. In that case, it refers to the data object as a whole:

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

node.getData('name'); // undefined
node.getData(); // {} (empty object)

node.setData({ type: 'company', name: 'Google' });
node.getData('name'); // "Google"
node.getData(); // {type: 'company', name: 'Google'}

ogma.addNodes([{ id: 1 }, { id: 2 }]).then(function (nodes) {
  nodes.fillData({ name: 'John', age: 48 });
  nodes.getData(); // [{name: 'John', age: 48}, {name: 'John', age: 48}]

  // In this case, be careful: the *same* object has been assigned to both nodes;
  // modifying one will modify the other (they have the same reference)
});

Working with nested properties

It's very common to work with nested properties:

js
const node = ogma.addNode({
  id: 0,
  data: {
    type: 'computer',
    properties: {
      name: 'SX-615',
      os: 'ubuntu-16.04',
      'hardware.ram': '16GB',
      'hardware.cpu': 'intel-core-i7'
    }
  }
});

The most straightforward way to specify a data property path is to use a string. If the string contains dots, it will be split around its dots to access the property:

js
console.log(node.getData('type')); // "computer"
console.log(node.getData('properties.name')); // "SX-615"

But what if the property name itself contains a dot?

js
console.log(node.getData('properties.hardware.ram')); // undefined

For that matter, you can also specify a property path using an array of strings:

js
console.log(node.getData(['type'])); // "computer"
console.log(node.getData(['properties', 'name'])); // "SX-615"
console.log(node.getData(['properties', 'hardware.ram'])); // "16GB"

Of course it also works with setData and fillData:

js
node.setData(['properties', 'hardware.ram'], '32GB');

Filtering

Filters are similar to the styling rules: these are global rules that hide nodes/edges depending on a criteria. You can add one with ogma.transformations.addNodeFilter (and addEdgeFilter):

js
// Simple case: hides all nodes that are not a company

ogma.setGraph({
  nodes: [
    {
      id: 0,
      data: { type: 'person', name: 'Norwood Preston', age: 44, sex: 'male' }
    },
    { id: 1, data: { type: 'person', name: 'Martine Avril', sex: 'female' } },
    { id: 2, data: { type: 'company', name: 'Canetrax', nbEmployees: 163 } },
    { id: 3, data: { type: 'company', name: 'Ventoplus', nbEmployees: 67 } },
    {
      id: 4,
      data: {
        type: 'country',
        name: 'USA',
        population: 325365189,
        currency: 'USD'
      }
    },
    {
      id: 5,
      data: {
        type: 'country',
        name: 'France',
        population: 66991000,
        currency: 'EUR'
      }
    },
    {
      id: 6,
      data: {
        type: 'country',
        name: 'Germany',
        population: 82175700,
        currency: 'EUR'
      }
    },
    {
      id: 7,
      data: { type: 'person', name: 'Hermenegild Dietrich', age: '32 years' }
    }
  ],
  edges: [
    { id: 0, source: 0, target: 4, data: { type: 'lives_in' } },
    { id: 1, source: 0, target: 2, data: { type: 'owns' } },
    { id: 2, source: 1, target: 5, data: { type: 'lives_in' } },
    { id: 3, source: 1, target: 3, data: { type: 'owns' } },
    { id: 4, source: 2, target: 3, data: { type: 'sells_to' } },
    { id: 5, source: 7, target: 6, data: { type: 'lives_in' } },
    { id: 5, source: 7, target: 3, data: { type: 'works_for' } }
  ]
});

ogma.transformations
  .addNodeFilter(function (node) {
    return node.getData('type') === 'company';
  })
  .whenReady(function () {
    // `getNodes` without any argument returns all nodes that are not filtered out
    console.log(ogma.getNodes().getId()); // [2, 3]
  });

The method returns a Filter object which can be used to delete or remove the filter:

js
const hiddenType = 'company';

const filter = ogma.transformations.addNodeFilter(function (node) {
  return node.getData('type') === hiddenType;
});

// ...

hiddenType = 'person';

// A variable referenced by the filter has changed, we need to refresh the filter
filter.refresh();

// ...

// Delete the filter
filter.destroy();

It is possible to retrieve all the node filters using getNodeFilters():

js
// Removes all the filters

ogma.getNodeFilters().forEach(function (filter) {
  filter.destroy();
});

Note: it is possible to have multiple filters at the same time. A node (or edge) must pass all the filters to stay visible (like Array.filter works).

Watching properties

Ogma provides a way to watch the data of the nodes and edges in the graph:

  • indicates which properties are in the graph (a node property is in the graph if at least on node has a value for it)
  • indicates the type of each property in the graph (number or not)
  • indicates what are the different values for each property, and the different values that this property has

You can watch a property two different ways:

  • as an object: you are interested in the sub-properties of that property. It is assumed that this property is always an object
  • as a value: you want to keep track of the values for that specific property, even if it is an object.

The following example explains it better:

js
ogma.setGraph({
  nodes: [
    {
      id: 0,
      data: {
        type: 'person',
        name: 'Norwood Preston',
        age: 44,
        sex: 'male',
        bloodType: 'A'
      }
    },
    { id: 1, data: { type: 'person', name: 'Martine Avril', sex: 'female' } },
    { id: 2, data: { type: 'company', name: 'Canetrax', nbEmployees: 163 } },
    { id: 3, data: { type: 'company', name: 'Ventoplus', nbEmployees: 67 } },
    {
      id: 4,
      data: {
        type: 'country',
        name: 'USA',
        population: 325365189,
        currency: 'USD'
      }
    },
    {
      id: 5,
      data: {
        type: 'country',
        name: 'France',
        population: 66991000,
        currency: 'EUR'
      }
    },
    {
      id: 6,
      data: {
        type: 'country',
        name: 'Germany',
        population: 82175700,
        currency: 'EUR'
      }
    },
    {
      id: 7,
      data: { type: 'person', name: 'Hermenegild Dietrich', age: '32 years' }
    }
  ],
  edges: [
    { id: 0, source: 0, target: 4, data: { type: 'lives_in' } },
    { id: 1, source: 0, target: 2, data: { type: 'owns' } },
    { id: 2, source: 1, target: 5, data: { type: 'lives_in' } },
    { id: 3, source: 1, target: 3, data: { type: 'owns' } },
    { id: 4, source: 2, target: 3, data: { type: 'sells_to' } },
    { id: 5, source: 7, target: 6, data: { type: 'lives_in' } },
    { id: 5, source: 7, target: 3, data: { type: 'works_for' } }
  ]
});

// By specifying no argument, we watch the sub-properties of the data object
const dataWatcher = ogma.schema.watchNodeObjectProperty();

console.log(dataWatcher.getProperties()); // ['type', 'name', 'nbEmployees', 'age', 'sex', 'currency', 'population', 'bloodType']

const populationInfo = console.log(watcher.getPropertyInfo('population'));

console.log(populationInfo.getBoundaries()); // {min: 66991000, max: 325365189}
console.log(populationInfo.getCount()); // 3
console.log(populationInfo.getType()); // "number"
console.log(populationInfo.getValueCount(66991000)); // 1
console.log(populationInfo.getValues()); // [325365189, 66991000, 82175700]

dataWatcher.onPropertyAdded(function (propertyName) {
  console.log('data property "' + propertyName + '" has been added');
});

dataWatcher.onPropertyUpdated(function (propertyName) {
  console.log('data property "' + propertyName + '" has been updated');
});

dataWatcher.onPropertyRemoved(function (propertyName) {
  console.log('data property "' + propertyName + '" has been removed');
});

ogma.addNode({
  id: 8,
  data: { type: 'country', name: 'Italy', density: 201.3 }
});

// Prints:
// 'data property "density" has been added'
// 'data property "type" has been updated' (because the number of nodes for which the value is "country" increased)
// 'data property "name" has been updated' (because there is a new value for that property)

ogma.removeNode(0);

// Prints:
// 'data property "bloodType" has been removed'

// Stop the watcher from being updated
dataWatcher.kill();

// Watch a single property that is not an object
const currencyWatcher = ogma.schema.watchNodeNonObjectProperty('currency');

// Contrary to the object version, this method doesn't take any argument
const currencyInfo = currencyWatcher.getPropertyInfo();

console.log(currencyInfo.getBoundaries()); // null (the property is not a number)
console.log(currencyInfo.getCount()); // 3 (3 nodes have this property)
console.log(currencyInfo.getType()); // "any" (the property is not a number)
console.log(currencyInfo.getValueCount('EUR')); // 2 (two nodes have 'EUR' as the value for this property
console.log(currencyInfo.getValues()); // ['USD', 'EUR']

currencyWatcher.onUpdate(function (propertyInformation) {
  // the argument is the object returned by `getPropertyInfo()`
  console.log(
    'values for property "currency" are now: ' + propertyInformation.getValues()
  );
});

// Assume for a moment that France goes back to its old currency
ogma.getNode(5).setData('currency', 'XPF');

// Prints:
// 'values for property "currency" are now: ["USD", "EUR", "XPF"]'