GraphML

The GraphML file format results from the joint effort of the graph drawing community to define a common format for exchanging graph structure data. It uses an XML-based syntax and supports the entire range of possible graph structure constellations including, most notably, grouped graphs.

What GraphML Looks Like

An abbreviated excerpt of a GraphML file that shows the encoding of a graph is presented in Example 9.3, “Abbreviated GraphML representation”. The basic graph structure is encoded using the GraphML elements <graph>, <node>, and <edge>. Each of these elements has an XML attribute id whose value is used to uniquely identify graphs, nodes, and edges within a GraphML file. XML attributes source and target which are part of the <edge> element, reference unique node IDs to indicate both source and target node of an edge.

Example 9.3, “Abbreviated GraphML representation” also shows yFiles-specific enhancements to the GraphML file format that describe the visual representation of a graph. These enhancements are nested within the <data> element of a node or an edge.

Example 9.3. Abbreviated GraphML representation

<?xml version="1.0" encoding="UTF-8"?>
<!-- This file was written by the JAVA GraphML Library. -->
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
           http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"
         xmlns:y="http://www.yworks.com/xml/graphml">
  <key id="d0" for="node" yfiles.type="nodegraphics"/>
  <key id="d1" for="edge" yfiles.type="edgegraphics"/>
  <graph id="G" edgedefault="directed">
    <node id="n0">
      <data key="d0">
        <y:ShapeNode>
          <y:Geometry x="170.5" y="-15.0" width="59.0" height="30.0"/>
          <y:Fill color="#CCCCFF" transparent="false"/>
          <y:BorderStyle type="line" width="1.0" color="#000000"/>
          <y:NodeLabel>January</y:NodeLabel>
          <y:Shape type="rectangle"/>
        </y:ShapeNode>
      </data>
    </node>
    <node id="n1"/>
    <edge id="e1" source="n1" target="n0">
      <data key="d1">
        <y:PolyLineEdge>
          <y:Path sx="0.0" sy="-15.0" tx="29.5" ty="0.0">
            <y:Point x="425.0" y="0.0"/>
          </y:Path>
          <y:LineStyle type="line" width="1.0" color="#000000"/>
          <y:Arrows source="none" target="standard"/>
          <y:EdgeLabel>Happy New Year!</y:EdgeLabel>
          <y:BendStyle smoothed="false"/>
        </y:PolyLineEdge>
      </data>
    </edge>
  </graph>
</graphml>

The GraphML file format is also described in the GraphML Primer. The definition of the XML structure can be seen from the schema documentation or from the XML schema definition file graphml-structure.xsd.

Reading and Writing GraphML

Class GraphMLIOHandler from package y.io provides I/O support for the GraphML file format. It is a direct subclass of abstract class IOHandler, see also Figure 9.2, “GraphML I/O handler class hierarchy”.

Figure 9.2. GraphML I/O handler class hierarchy

GraphML I/O handler class hierarchy.

The code fragment in Example 9.4, “Instantiating a GraphMLIOHandler” shows how to instantiate and use a GraphMLIOHandler to write an instance of type Graph2D to a GraphML-encoded file. Class ZipGraphMLIOHandler can be used in a similar manner to write Zip-compressed GraphML files which are up to 50 times smaller than their uncompressed counterparts.

Example 9.4. Instantiating a GraphMLIOHandler

public void encodeAsGraphML(Graph2D graph)
{
  // Instantiate a GraphML I/O handler and write the graph to file.
  try {
    IOHandler ioh = new GraphMLIOHandler();
    ioh.write(graph, "MyGraphML.graphml");
  }
  catch (IOException ioEx) {
    // Something went wrong. Complain.
  }
}

Reading and writing GraphML is done by the read and write methods.

Reading and Writing Additional Data

GraphML allows to associate in form of a GraphML document fragment to a graph element. This section demonstrates the GraphML format for associated data and how to use the yFiles library to write associated data in a GraphML file.

GraphML Default Extension Mechanism

The GraphML default extension mechanism allows to declare so-called GraphML attributes that can be used to conveniently store additional information in a GraphML file. In its original form, this mechanism directly supports additional data of type boolean, int, long, float, double, and String. By means of customization to the default mechanism, however, GraphML attributes can be used to store data of arbitrary complexity and type.

A GraphML attribute is declared using the <key> element which comprises a unique identifier as well as scope, name, and the domain of values (i.e., the actual type) for the attribute. Table 9.3, “XML attributes for GraphML <key> element” lists the set of XML attributes and describes their function within the declaration of a GraphML attribute. Note that in a GraphML file, <key> elements must be placed before any <graph> element.

Table 9.3. XML attributes for GraphML <key> element

XML Attribute Value Domain Description
id NMTOKEN Uniquely identifies the GraphML attribute declaration within a GraphML file. Required to enable the GraphML attribute look-up mechanism.
for one of "graph", "node", "edge", or "port" Determines the scope of the GraphML attribute.
attr.name NMTOKEN Identifying name for the GraphML attribute that can be used by an application.
attr.type one of "boolean", "int", "long", "float", "double", or "string" Determines the domain for the values (i.e., the actual type) of the GraphML attribute.

Actual values for a GraphML attribute are defined using the <data> element which is nested within the GraphML elements <graph>, <node>, <edge>, or <port>. Required with each <data> element is XML attribute key which is used by a look-up mechanism to find the proper GraphML attribute declaration for the contents of a given <data> element.

This GraphML attribute look-up mechanism is responsible for matching the value given for the key attribute of a <data> element to the unique ID defined by the id attribute of a <key> element. The mapping that is established by this match is essential for delegating the parsing of a given <data> element to the proper parser code that handles the specific contents.

The GraphML default extension mechanism can be customized to enable GraphML attributes that hold arbitrarily complex data. GraphMLIOHandler uses this mechanism to read and write additional data of simple and complex type. The basic technique to enhance the extension mechanism is to define a new XML attribute with the <key> element and nest user-defined XML elements inside the <data> element.

The <key> elements define a unique id which matches the key attribute of the corresponding <data> elements. The following GraphML example has <data> elements which encode data for nodes and which are defined in the <key> element with the id "d0".

Example 9.5. Outline of a GraphML file with GraphML attribute of simple type

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="...">
...
<!-- Definition of a GraphML attribute to store additional data for a -->
<!-- graph's nodes. -->
  <key id="d0" for="node" attr.name="boolean-value" attr.type="boolean"/>

  <graph id="G" edgedefault="directed">
    ...
<!-- A node that has a <data> element referring to the GraphML attribute -->
<!-- "d0." The node's value (of type boolean) is "true." -->
    <node id="n0">
      <data key="d0">true</data>
    </node>
    <node id="n1">
      <data key="d0">false</data>
    </node>
    ...
  </graph>
</graphml>

The XML structure of the enhanced GraphML file format as defined by the yFiles GraphML engine can be seen from the following XML schema definition files:

  • ygraphml.xsd declares additional XML elements to be nested within GraphML <data> elements
  • ygraphics.xsd defines new XML elements that encode predefined yFiles realizer implementations and also all kinds of data types to be used with these newly defined XML elements

Direct Support for Simple Data Types

GraphMLIOHandler delegates most of the work to a core implementation, the GraphMLHandler. That handler can be obtained using the method getGraphMLHandler(). Most aspects of GraphML reading and writing, e.g. adding attributes as shown in the next chapter, have to be configured at the core handler instead of GraphMLIOHandler itself.

Example 9.6. Getting the core GraphMLHandler

GraphMLIOHandler ioh = new GraphMLIOHandler();
GraphMLHandler core = ioh.getGraphMLHandler();

Both reading and writing of additional data that is of simple types (like boolean, int, long, float, double, or String) is supported transparently. This means, an DataProvider that holds int values, for example, can be serialized to a GraphML attribute with minimal effort using the convenience functionality provided by GraphMLIOHandler (the same applies for the deserialization of a corresponding DataAcceptor).

Important

The yFiles GraphML engine makes use of the data accessor implementations of the yFiles library to store the values of a GraphML attribute. However, these implementations provide no support for either TYPE_LONG or TYPE_FLOAT. Therefore, when storing the values of a GraphML attribute in a given data acceptor, TYPE_LONG is type-casted to int and TYPE_FLOAT is type-casted to double.

As already mentioned above, additional data of simple type is written in a GraphML file as shown in Example 9.5, “Outline of a GraphML file with GraphML attribute of simple type”.

For data consisting of structured types, reading and writing requires a (de)serializer to be registered. Custom (de)serialization is described in the section called “Serialization and Deserialization of Structured Data”. the section called “Reading and Writing Data Using DataProviders and DataAcceptors as Data Holders” describes how the custom (de)serialization can be applied to write a complex mapped attribute. The following section handles mapped attributes of simple type.

Reading and Writing Data Using DataProviders and DataAcceptors as Data Holders

The GraphML framework supports reading additional data most conveniently when using DataProvider and DataAcceptor instances to store the retrieved data. This functionality is built on top of the general serialization and deserialization support. GraphMLHandler provides a number of convenience methods that enable registering DataAcceptors as input data targets:

The DataAcceptor instances bind the data stored in GraphML attributes to their corresponding graph elements. The specified name must correspond to the attr.name of the GraphML attribute's key definition.

Class KeyScope defines constants which define the "scope" of the attribute, e.g. KeyScope.NODE or KeyScope.EDGE. In the corresponding GraphML <key> element the scope is defined in the for attribute.

Similarly, class KeyType defines constants which define the "type" of the attribute data, e.g. KeyType.STRING or KeyType.INT. In the corresponding GraphML <key> element the type is defined in the attr.type attribute. If no key type is defined, the attribute is treated as complex (i.e. structured) data.

Example 9.7, “Adding a GraphML attribute for reading to the GraphMLIOHandler” shows how to register a data acceptor as input target for a GraphML attribute with node scope that stores boolean values.

Example 9.7. Adding a GraphML attribute for reading to the GraphMLIOHandler

//  <key id="d0" for="node" attr.name="boolean-value" attr.type="boolean"/>
GraphMLHandler core = ioHandler.getGraphMLHandler();
core.addInputDataAcceptor("boolean-value",
                          node2boolMap,
                          KeyScope.NODE,
                          KeyType.BOOLEAN);

As an alternative to registering already instantiated DataAcceptor instances as the input targets, GraphMLHandler also supports registering DataAcceptor "futures" using the class Future. Basically, "future" means that DataAcceptor instances are created lazily depending on whether there is actually a GraphML attribute with a matching attr.name specified in the GraphML file that is read. The following methods can be used to declare DataAcceptor "futures" when reading GraphML.

Future addInputDataAcceptorFuture(
          String name,
          KeyScope scope,
          KeyType type)
Description Registers a "future" DataAcceptor for lazy creation

To write data from a DataProvider to a GraphML attribute, GraphMLHandler provides a set of methods to register the DataProvider instance as an output data source that can be used analogously to the attribute input methods:

Example 9.8, “Adding a GraphML attribute for writing to the GraphMLIOHandler” shows how to register a data provider writing a GraphML attribute with node scope and boolean values.

Example 9.8. Adding a GraphML attribute for writing to the GraphMLIOHandler

//  <key id="d0" for="node" attr.name="boolean-value" attr.type="boolean"/>

// node2boolMap is a DataMap which implements
// both DataAcceptor and DataProvider
GraphMLHandler core = ioHandler.getGraphMLHandler();
core.addOutputDataProvider("boolean-value",
                          node2boolMap,
                          KeyScope.NODE,
                          KeyType.BOOLEAN);

Support for Structured Data

Serialization and Deserialization of Structured Data

Serialization and deserialization logic for structured data can be provided by means of event listeners. Serialization for custom data will be handled by a SerializationHandler, deserialization by a DeserializationHandler. Handling complex data mapped in a DataProvider / DataAcceptor is described in the section called “Reading and Writing Data Using DataProviders and DataAcceptors as Data Holders”. In addition, addSerializationHandler and addDeserializationHandler available with the GraphMLHandler can also be used to attach event listeners that provide custom (de)serialization logic. These event handlers can be used to realize general support for structured types in nested XML content of GraphML attribute data. The listeners will be invoked when serialize and deserialize are invoked on the GraphMLWriteContext or GraphMLParseContext, respectively.

Example 9.9, “A simple class to be serialized” shows a simple class with a single String value.

Example 9.9. A simple class to be serialized

public static class Item {
  private String value;

  public Item(String value) {
    this.value = value;
  }

  public String getValue() {
    return value;
  }
}

The GraphML element which can be used to represent the example class is shown in Example 9.10, “The GraphML element which represents the class Item”. The value can be represented as String, so it is best to serialize it as an attribute.

Example 9.10. The GraphML element which represents the class Item

<Item value="MyValue"/>

In order to handle the example Item class, a matching serializer (i.e. a SerializationHandler) and deserializer (DeserializationHandler) have to be registered at the GraphMLHandler as shown in Example 9.11, “Registration of a custom serializer/deserializer”.

Example 9.11. Registration of a custom serializer/deserializer

GraphMLIOHandler ioHandler = new GraphMLIOHandler();
GraphMLHandler core = ioHandler.getGraphMLHandler();
// register serializer and deserializer
core.addSerializationHandler(new ItemSerializer());
core.addDeserializationHandler(new ItemDeserializer());

A SerializationHandler has to implement the method onHandleSerialization(SerializationEvent event). The SerializationEvent holds the necessary information to serialize an object. The object to deserialize can be obtained by getItem(), the XmlWriter instance which writes the GraphML can be obtained by getWriter(). If necessary, getContext() yields the GraphMLWriteContext.

The serializer has to verify whether it can handle the given item. If that is the case, it uses the given writer to write the XML. Important: after writing the XML, the event has to be marked as handled, so no other serializer will handle the item.

An example SerializationHandler implementation which handles the serialization of the example class Item is shown in Example 9.12, “A custom serializer for the example class Item”.

Example 9.12. A custom serializer for the example class Item

public class ItemSerializer implements SerializationHandler {

  public void onHandleSerialization(SerializationEvent event)
                                      throws GraphMLWriteException {
    // get the item to serialize
    Object o = event.getItem();
    // if this serializer can handle the item: handle it
    // otherwise do nothing
    if (o instanceof Item) {
      Item item = (Item) o;
      // Use the writer to write the Item element
      XmlWriter writer = event.getWriter();
      writer.writeStartElement("Item", null);
      writer.writeAttribute("value", item.getValue());
      writer.writeEndElement();
      // The Item is serialized: Mark the event as handled
      event.setHandled(true);
    }
  }
}

Creating a custom serializer can be facilitated by using the abstract implementation AbstractSerializer. Using this class as base, developers have to implement the methods

The deserializer for the Item class is shown in Example 9.13, “A custom deserializer for the example class Item”. Similar to the serializer, it has to test whether it can handle the element, which usually is determined by the namespace and the name of the given element. If the element cannot be handled, the deserializer does nothing, if it can, the deserializer has to create a new instance of the class to deserialize and pass it as result to the event.

Example 9.13. A custom deserializer for the example class Item

public class ItemDeserializer implements DeserializationHandler {
  public void onHandleDeserialization(DeserializationEvent event)
                                        throws GraphMLParseException {
    // get the element to parse
    org.w3c.dom.Node xmlNode = event.getXmlNode();
    // if the element can be parsed
    // create a new instance
    if (xmlNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
            && "Item".equals(xmlNode.getLocalName())) {
      // create a new instance with the value of the "value" attribute
      Item item = new Item(((Element) xmlNode).getAttribute("value"));
      // pass the new instance as result
      // Note: setting the result already marks the event as handled
      event.setResult(item);
    }
  }
}

Creating a custom serializer can be facilitated by using the abstract implementation AbstractDeserializer. Using this class as base, developers have to implement the methods

Reading and Writing Data Using DataProviders and DataAcceptors as Data Holders

GraphMLHandler supports adding input data acceptors and output data providers together with a DeserializationHandler and SerializationHandler, respectively:

Dynamic Registration of Attributes

The general scheme for serialization and deserialization of data types uses so-called output handlers and input handlers. These handlers are responsible for writing out, respectively reading in (structured) data. The reading and writing mechanism discussed so far uses predefined input and output handler implementations behind the scenes.

In most cases it is sufficient to rely on these implementation. However, using input and output handlers provides more flexibility in data handling like dynamic registering of such handlers: Event listener for registering both output handlers and input handlers can be attached to the GraphMLHandler using addInputHandlerProvider() and addOutputHandlerProvider() events, respectively. They are invoked by GraphMLHandler dynamically, i.e., at the time of writing or reading.

A InputHandlerProvider allows registering an input handler based on the GraphML data to parse (or to be more exact: depending on the <key> definitions). The listener has to verify whether the QueryInputHandlersEvent is not already handled and whether scope and name of the attribute match the handler's definitions. If that is the case, it registers the appropriate input handler and initializes it as shown in Example 9.14, “Attaching input handlers to a GraphMLIOHandler instance”. Registering the input handler marks the event as handled, so no other input handler will be registered for that key.

Example 9.14. Attaching input handlers to a GraphMLIOHandler instance

// ioHandler is of type GraphMLIOHandler
GraphMLHandler core = ioHandler.getGraphMLHandler();
core.addInputHandlerProvider(new InputHandlerProvider() {
  public void onQueryInputHandler(QueryInputHandlersEvent event)
                                      throws GraphMLParseException {
    Element keyDefinition = event.getKeyDefinition();
    // Test whether the event is not yet handled
    // and whether the key definitions are matching the handler
    if (!event.isHandled()
        && GraphMLHandler.matchesScope(keyDefinition, KeyScope.EDGE)
        && GraphMLHandler.matchesName(keyDefinition, "myEdgeAttribute")) {
      MyInputHandler handler = new MyInputHandler();
      // initialize the handler
      handler.initializeFromKeyDefinition(event.getContext(), keyDefinition);
      // register the handler
      event.addInputHandler(handler);
    }
  }
});

A OutputHandlerProvider allows registering an output handler dynamically. It is invoked each time GraphMLHandler starts a write process. An output handler can be registered as shown in Example 9.15, “Attaching output handlers to a GraphMLIOHandler instance”.

Example 9.15. Attaching output handlers to a GraphMLIOHandler instance

// ioHandler is of type GraphMLIOHandler
GraphMLHandler core = ioHandler.getGraphMLHandler();
core.addOutputHandlerProvider(new OutputHandlerProvider() {
  public void onQueryOutputHandler(QueryOutputHandlersEvent event)
                                        throws GraphMLWriteException {
      event.addOutputHandler(new MyOutputHandler(), KeyScope.EDGE);
  }
});

Output handlers and input handlers are implementations of interfaces OutputHandler and InputHandler, respectively. For both interfaces there are abstract implementations available which serve as convenient base classes:

The predefined classes ComplexDataProviderOutputHandler and ComplexDataAcceptorInputHandler provide the default support for writing and reading structured data that is held by DataProvider and DataAcceptor instances.

The tutorial demo application ComplexExtensionGraphMLDemo.java shows how to read and write structured data.

Support for Custom Realizer Implementations

The yFiles GraphML engine enables convenient (de)serialization of node and edge realizers by means of so-called realizer serializers. Realizer serializers encapsulate the logic for encoding and parsing the different kinds of realizer implementations available with the yFiles library.

Figure 9.3, “GraphML node realizer serializer class hierarchy” depicts the hierarchy of node realizer serializer classes that are predefined by the yFiles GraphML engine. Note that it reflects the hierarchy of the node realizer classes from package y.view.

Figure 9.3. GraphML node realizer serializer class hierarchy

GraphML node realizer serializer class hierarchy.

Note

The hierarchy of predefined edge realizer serializer classes is analogous to that of the node realizer serializer classes as shown in Figure 9.3, “GraphML node realizer serializer class hierarchy”.

Support for Custom Realizer

Adding support for custom realizer implementations to the GraphML file format can be done by registering appropriate custom serializers with the GraphMLIOHandler. GraphMLIOHandler provides a number of methods to register a RealizerSerializer:

void addNodeRealizerSerializer(NodeRealizerSerializer nrs)
void addEdgeRealizerSerializer(EdgeRealizerSerializer ers)
Description Methods for registering serializers for custom node and edge realizers

GraphMLIOHandler delegates the management of realizer serializers to its core Graph2DGraphMLHandler, which also offers methods to register realizer serializers. Furthermore, the realizer serializers can also be removed from the Graph2DGraphMLHandler.

void addNodeRealizerSerializer(NodeRealizerSerializer nrs)
void addEdgeRealizerSerializer(EdgeRealizerSerializer ers)
Description Methods for registering serializers for custom node and edge realizers
void removeNodeRealizerSerializer(NodeRealizerSerializer nrs)
void removeEdgeRealizerSerializer(EdgeRealizerSerializer ers)
Description Methods for removing serializers for custom node and edge realizers

The tasks of a serializer are similar to those described for serializers and deserializers in the section called “Serialization and Deserialization of Structured Data”. The encoding part of a serializer is responsible for generating any user-defined XML elements which are nested within GraphML <data> elements. The parsing part extracts its data by processing the DOM (Document Object Model) structure of a GraphML file starting at the DOM node that represents a GraphML <data> elements.

It is recommended to use the abstract classes AbstractNodeRealizerSerializer and AbstractEdgeRealizerSerializer as base for custom realizer serializers. These classes already handle the (usually) common tasks like reading and writing labels or the geometry.

A node or edge realizer serializer which is based on the abstract classes has to implement the following methods:

Tutorial demo application CustomNodeRealizerSerializerDemo.java, which uses CustomNodeRealizerSerializer.java, shows the code of a custom realizer serializer, how it is registered with the RealizerSerializerManager of a GraphMLIOHandler instance, and also how it is used.

Support for GenericNodeRealizer and GenericEdgeRealizer

A GenericNodeRealizer or GenericEdgeRealizer is serialized automatically by writing out its configuration name. See the section called “Providing Interface Implementations” for details about customizing a generic node or edge realizer. The example configuration in that chapter which is registered with the name "Ellipse" will be serialized as <y:GenericNode configuration="Ellipse">.

When reading GraphML developers have to make sure that a generic node or edge realizer configuration with the serialized configuration name exists. If not a configuration with that name will be created using the default implementations.

The style properties and user data associated with the realizer will be (de)serialized automatically. Simple types will be (de)serialized without further configuration. For complex types, a matching SerializationHandler and DeserializationHandler have to be registered at the GraphMLHandler as described in the section called “Serialization and Deserialization of Structured Data”.

Advanced Topics

yFiles XSLT Support for GraphML

The yFiles GraphML engine provides advanced conversion services for arbitrary XML-based data. By means of a suitable XSLT stylesheet any valid XML file can be transformed into a graph structure encoded in GraphML file format.

The tutorial demo application XmlXslDemo.java shows how to import XML files as GraphML. Predefined XSLT stylesheets are available for the following XML-based file types and information:

Reading and Writing Graphs of Arbitrary Type

The most convenient way to use the yFiles GraphML engine for reading and writing GraphML file format is by using the services of class GraphMLIOHandler. However, being a true yFiles IOHandler, this class requires a Graph2D object when reading or writing a file. Using the class GraphMLHandler it is also possible to read and write graphs of arbitrary type.

Tutorial Demo Code

Loading and saving graphs in GraphML file format is demonstrated in the tutorial demo application GraphMLDemo.java. SimpleAttributesDemo.java shows a detailed example of how the GraphML default extension mechanism can be used to read and write simple data types.

For examples how customized encoding and parsing logic can be provided, please see the following tutorial demo applications:

XmlXslDemo.java shows how XML files can imported as GraphML by the means of an XSLT stylesheet.