Skip to content
  1. Tutorials
  2. Using data API

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"]'