Using the yFiles GraphML Extension Package

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

Figure 10.1. GraphML I/O handler class hierarchy

GraphML I/O handler class hierarchy.

The code fragment in Example 10.5, “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 10.5. 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. 
  }
}

Direct Support for Simple Data Types

Using the GraphML default extension mechanism to add data of simple type such as boolean, int, long, float, double, or String, can be done by means of the methods from class GraphMLIOHandler as listed in API Excerpt 10.1, “GraphMLIOHandler support for the GraphML default extension mechanism”.

API Excerpt 10.1. GraphMLIOHandler support for the GraphML default extension mechanism

// Storing additional data with a graph. 
void addGraphAttribute(DataProvider out, DataAcceptor in, String attrName, 
                       int type)

// Storing additional data with the nodes of a graph. 
void addAttribute(NodeMap map, String attrName, int type)
void addNodeAttribute(DataProvider out, DataAcceptor in, String attrName, 
                      int type)

// Storing additional data with the edges of a graph. 
void addAttribute(EdgeMap map, String attrName, int type)
void addEdgeAttribute(DataProvider out, DataAcceptor in, String attrName, 
                      int type)

These methods are used to register the data accessors that will be accessed whenever any data is actually read from or written to a GraphML file. They also determine the scope of a GraphML attribute (i.e., whether it holds values for a graph, the nodes, or the edges), its identifying name, and the domain for its values. Note that this domain, i.e., the actual type of the GraphML attribute, can be set using the constants TYPE_BOOLEAN, TYPE_INT, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, and TYPE_STRING defined in interface AttributeConstants.

Example 10.6. Adding a GraphML attribute to GraphMLIOHandler

void addNodeColorGraphMLAttribute(GraphMLIOHandler handler, NodeMap nm)
{
  // The given node map will be accessed by the reader and writer classes 
  // whenever a GraphML file is read or written. 
  handler.addAttribute(nm, "color", AttributeConstants.TYPE_STRING);
}

Example 10.6, “Adding a GraphML attribute to GraphMLIOHandler” shows how to define a GraphML attribute that stores additional data of simple type for all nodes of a graph. Example 10.7, “Outline of a GraphML file with GraphML attribute of simple type” is an excerpt of a GraphML file that shows both the resulting GraphML attribute declaration and also the definition of its values with each of the nodes.

Example 10.7. 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="color" attr.type="string"/>
  
  <graph id="G" edgedefault="directed">
    ...
    <!-- A node that has a <data> element referring to the GraphML attribute -->
    <!-- "d0." The node's value (of type string) is "green." -->
    <node id="n0">
      <data key="d0">green</data>
    </node>
    <node id="n1">
      <data key="d0">black</data>
    </node>
    ...
  </graph>
</graphml>

Important

The yFiles GraphML extension package 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.

General Support for Structured Data

Storing structured data, i.e., more than just simple data types with a GraphML attribute is delegated by GraphMLIOHandler to subordinate input and output handlers that are specialized for handling specific types of complex data, like, e.g., realizer-related information. These subordinate handlers are implementations of interfaces OutputHandler and DOMInputHandler that are registered with class GraphMLIOHandler. They are responsible both for serializing and deserializing the structured data and also for defining an additional XML attribute that is used to enhance the <key> element as described in Customizing the GraphML Extension Mechanism.

The methods listed in API Excerpt 10.2, “Getting subordinate input and output handlers registered with GraphMLIOHandler” can be used to get all subordinate input and output handlers for either graphs, nodes, or edges that are registered with a GraphMLIOHandler. New custom handlers can be registered by simply adding them to one of the returned collections.

API Excerpt 10.2. Getting subordinate input and output handlers registered with GraphMLIOHandler

// Getter methods for the registered subordinate input and output handlers of a 
// GraphMLIOHandler instance. 
Collection getInputHandlers()
Collection getOutputHandlers(int scope)

Note that the actual scope for input handlers is determined automatically at parse time while the scope for output handlers has to be set using the constants SCOPE_GRAPH, SCOPE_NODE, and SCOPE_EDGE defined in interface GraphMLConstants.

Example 10.8, “Adding custom subordinate handlers with node scope” shows how custom handlers with node scope are registered with a GraphMLIOHandler.

Example 10.8. Adding custom subordinate handlers with node scope

// 'handler' is of type GraphMLIOHandler. 

// Register a custom subordinate output handler for nodes. 
MyNodeOutputHandler nodeOut = new MyNodeOutputHandler();
handler.getOutputHandlers(GraphMLConstants.SCOPE_NODE).add(nodeOut);

// Register a custom subordinate input handler for nodes. 
MyNodeInputHandler nodeIn = new MyNodeInputHandler();
handler.getInputHandlers().add(nodeIn);

Table 10.4, “Helper classes for GraphML (de)serialization” lists useful helper classes for (de)serializing data to/from a GraphML file. They provide convenient support for generating XML-conformant content and parsing valid XML data for many data types commonly used by the yFiles library.

Table 10.4. Helper classes for GraphML (de)serialization

Classname Description
DomXmlWriter Implementation of interface XmlWriter that is used for writing GraphML. This class is used by GraphMLIOHandler by default.
GraphicsSerializationToolkit Can be used for reading and writing GraphML. Offers methods to (de)serialize yFiles-specific types and also common data types such as java.awt.Color or java.awt.Insets.

Subordinate Output Handlers

Subordinate output handlers are responsible for introducing an appropriate XML attribute with the GraphML <key> element and for generating any user-defined XML elements which are nested within GraphML <data> elements. The XML attribute serves as the identifying marker to choose the corresponding parser logic when the GraphML attribute declaration has been parsed. The user-defined XML elements encode the respective structured data. Abstract class AbstractOutputHandler provides a convenient base class for actual implementations. Figure 10.2, “GraphML output handler class hierarchy” shows the class hierarchy of predefined output handler implementations.

Figure 10.2. GraphML output handler class hierarchy

GraphML output handler class hierarchy.

Table 10.5, “Predefined output handlers” lists the predefined output handlers of the yFiles GraphML extension package.

Table 10.5. Predefined output handlers

Classname Description
WriteNodeRealizerHandler Dispatches the serialization of node realizer data to so-called node realizer serializers; is registered with GraphMLIOHandler by default.
WriteEdgeRealizerHandler Dispatches the serialization of edge realizer data to so-called edge realizer serializers; is registered with GraphMLIOHandler by default.
PostprocessorOutputHandler Serializes structured data related to post-processor configuration; is registered with GraphMLIOHandler by default.
PortConstraintOutputHandler Serializes structured data related to port constraints and edge groups; has to be registered with GraphMLIOHandler.

Example 10.9, “Custom subordinate output handler with node scope” presents a custom output handler with node scope that writes user-defined data to a GraphML file. Note that the content of the <data> element is written using the output method printDataOutput of abstract class AbstractOutputHandler which has extended signature.

AbstractOutputHandler's method with extended signature is a convenience that enables easy access to the actual graph and the graph element that is to be written. The graph element is either a node or an edge according to the scope of the output handler, hence it can safely be type-casted to y.base.Node or y.base.Edge, respectively.

Example 10.9. Custom subordinate output handler with node scope

public class MyNodeOutputHandler extends AbstractOutputHandler {
  public void printKeyAttributes(GraphMLWriteContext context, 
                                 XMLWriter writer) {
    // Additional XML attribute to enhance the GraphML <key> element. 
    // The leading blank before the attribute's name is added by the writer of 
    // the <key> element.
    writer.writeAttribute("myCustomType", "true");
  }
  
  // Convenience method that provides access to the graph and also its nodes or 
  // edges according to the output handler's scope.
  public void printDataOutput(GraphMLWriteContext context, Graph graph, 
                              Object nodeedge, XMLWriter writer) {
    NodeRealizer nr = ((Graph2D)graph).getRealizer((y.base.Node)nodeedge);
    writer.writeStartElement("myData", null);
    writer.writeAttribute("value", nr.getHeight() * nr.getWidth());
    writer.writeEndElement();
  }
  
  // Not supported yet. 
  public void printKeyOutput(GraphMLWriteContext context, XMLWriter writer) {}
}

Subordinate Input Handlers

Subordinate input handlers are responsible for inspecting the set of XML attributes of GraphML <key> elements and for parsing the contents of GraphML <data> elements. The inspection yields whether an input handler provides parser logic that corresponds to a GraphML attribute declaration, i.e., whether the parser logic can handle the contents of GraphML <data> elements that refer to the given <key> element by matching its unique ID. If true, the GraphML parser logic branches to the input handler for parsing (the contents of) all such GraphML <data> elements. Abstract class AbstractDOMInputHandler provides a convenient base class for actual implementations. Figure 10.3, “GraphML DOM input handler class hierarchy” shows the class hierarchy of predefined DOM input handler implementations.

Figure 10.3. GraphML DOM input handler class hierarchy

GraphML DOM input handler class hierarchy.

Table 10.6, “Predefined input handlers” lists the predefined input handlers of the yFiles GraphML extension package.

Table 10.6. Predefined input handlers

Classname Description
ReadNodeRealizerHandler Dispatches the deserialization of node realizer data to so-called node realizer serializers; is registered with GraphMLIOHandler by default.
ReadEdgeRealizerHandler Dispatches the deserialization of edge realizer data to so-called edge realizer serializers; is registered with GraphMLIOHandler by default.
PostprocessorInputHandler Deserializes structured data related to post-processor configuration; is registered with GraphMLIOHandler by default.
PortConstraintInputHandler Deserializes structured data related to port constraints and edge groups; has to be registered with GraphMLIOHandler.

A subordinate input handler signals whether it can handle the contents of GraphML <data> elements through its acceptKey method. When parsing, it extracts the actual data by processing the DOM (Document Object Model) structure of a GraphML file. To this end it makes use of package org.w3c.dom, the component API of the Java API for XML Processing. Example 10.10, “Custom input handler with node scope” presents a custom input handler with node scope that reads user-defined data from a GraphML file. Note that the content of the <data> element is read using the input method parseData of abstract class AbstractDOMInputHandler which has extended signature.

AbstractDOMInputHandler's method with extended signature is a convenience that enables easy access to the actual graph and the graph element that is to be read. The graph element is either a node or an edge according to the scope of the input handler, hence it can safely be type-casted to y.base.Node or y.base.Edge, respectively.

Example 10.10. Custom input handler with node scope

public class MyNodeInputHandler extends AbstractDOMInputHandler {
  // Assume (parsing) responsibility only for those <data> elements that refer 
  // to GraphML attribute declarations with specific additional XML attribute. 
  public boolean acceptKey(NamedNodeMap map, int scopeType) {
    if (scopeType != GraphMLConstants.SCOPE_NODE) {
      return false;
    }
    // 'map' holds the XML attributes of a <key> element. 
    Node node = map.getNamedItem("myCustomType");
    return ((node == null) ? false : "true".equals(node.getNodeValue()));
  }
  
  // Parse the <data> element. 
  protected void parseData(DOMGraphMLParseContext context, Graph graph, 
                           Object nodeedge, boolean defaultMode, 
                           org.w3c.dom.Node domNode) {
    // Default mode is not supported. 
    if (defaultMode) {
      return;
    }
    
    String label = "";
    // 'domNode' holds the <data> element, its XML attributes, and all XML 
    // elements nested within. 
    org.w3c.dom.NodeList children = domNode.getChildNodes();
    if (children != null) {
      for (int i = 0; i < children.getLength(); i++) {
        org.w3c.dom.Node n = children.item(i);
        if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
            "myData".equals(n.getLocalName())) {
          label = parseMyDataElement(n);
        }
      }
    }
    ((Graph2D)graph).getRealizer((y.base.Node)nodeedge).setLabelText(label);
  }
  
  // Parse the attribute of <myData> element. 
  String parseMyDataElement(org.w3c.dom.Node domNode) {
    NamedNodeMap nm = domNode.getAttributes();
    org.w3c.dom.Node a = nm.getNamedItem("value");
    String txt = "Node's area is ";
    txt += ((a == null) ? "n/a." : "" + a.getNodeValue() + " square pixels.");
    return txt;
  }
  
  // Not supported yet. 
  protected void applyDefault(DOMGraphMLParseContext c, Graph g, Object noed) {}
}

Support for Custom Realizer Implementations

The yFiles GraphML extension package 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.

The serializers for both node realizer and edge realizer implementations are registered with the corresponding subordinate input and output handlers, i.e., with classes

These subordinate handlers are used by class GraphMLIOHandler and serve to dispatch both serialization and deserialization of specific realizer types to the matching serializer from the set of their registered serializers.

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

Figure 10.4. 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 10.4, “GraphML node realizer serializer class hierarchy”.

Adding support for custom realizer implementations to the GraphML file format can be done by registering appropriate custom serializers with the RealizerSerializerManager that is associated with a GraphMLIOHandler instance. API Excerpt 10.3, “Getter method for the RealizerSerializerManager of a GraphMLIOHandler instance” shows the getter method from class GraphMLIOHandler that returns the RealizerSerializerManager instance.

API Excerpt 10.3. Getter method for the RealizerSerializerManager of a GraphMLIOHandler instance

RealizerSerializerManager getRealizerSerializerManager()

API Excerpt 10.4, “Methods to register custom realizer serializers” lists the methods from class RealizerSerializerManager to register serializers for node and edge realizer implementations.

API Excerpt 10.4. Methods to register custom realizer serializers

void addNodeRealizerSerializer(NodeRealizerSerializer nrs)
void addEdgeRealizerSerializer(EdgeRealizerSerializer ers)

Any serializer that is registered using one of these methods is automatically added to the sets of serializers available with the corresponding input and output handlers of a given GraphMLIOHandler instance.

The tasks of a serializer are similar to those described for subordinate input and output handlers in Subordinate Input Handlers and Subordinate Output Handlers, respectively. 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.

Key to correctly support a custom realizer implementation with the GraphML file format is the proper definition of the XML element that is top-level inside the GraphML <data> element. This XML element is written by the subordinate output handler that initiates encoding and read by the subordinate input handler that initiates parsing of the data associated with a realizer. It serves as a means to enclose and also identify the data of a specific realizer implementation. Method getName, which is defined by both node serializer and edge serializer interfaces, is used to define the enclosing top-level element. It is invoked by both output handler and input handler, respectively:

  • When a GraphML file is written, the String object returned is used by the output handler as the name of the top-level XML element. (Note that method writeAttributes can be used to define additional XML attributes with the element.)
  • When a GraphML file is read, the String object returned is used by the input handler to find the matching realizer serializer that knows how to parse the data of a specific realizer.

Once the matching realizer serializer is determined, the input handler uses method createRealizerInstance to instantiate a new realizer. This realizer object is then configured using the data from the GraphML file.

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.

yFiles GraphML Post-Processors

The yFiles GraphML extension package provides support for advanced graph post-processing functionality. So-called post-processors are embedded statements in a GraphML file that reference code which is executed after the graph defined in the file is parsed. They allow to conveniently manipulate essential aspects of a GraphML-encoded graph directly following the parsing process and prior to display.

For example, post-processors can be used to automatically compute a layout for a freshly parsed graph. Consequently, post-processors come in handy when graphs are automatically generated from data that inherently provide only structural information, i.e., that lack coordinates or even dimensional information.

A post-processor can be any class that inherits from YModule which most notably includes the layout modules from package y.module, but also some classes from package yext.graphml.processor. Note however, that the services of a post-processor are not limited to providing automatic layout to a graph, resizing its nodes, or setting the background of a view. In fact, any kind of code can be part of such a class.

Execution of a post-processor is started by calling its main startup method start(Graph2D) which is defined in class YModule. The Graph2D object that is given as the parameter is the freshly parsed graph from the GraphML file.

The post-processor definitions are embedded in a GraphML file by means of a GraphML attribute that holds structured data. Example 10.11, “yFiles-specific GraphML attribute for post-processors” shows the combination of values for XML attributes yfiles.type and for that is used to declare the post-processor GraphML attribute with graph scope.

Example 10.11. yFiles-specific GraphML attribute for post-processors

<!-- Support for post-processors for a graph. -->
<key id="d2" for="graph" yfiles.type="postprocessors"/>

Example 10.12, “Post-processor setup” shows the general scheme of embedding post-processor definitions into a GraphML file. The <y:Postprocessors> element nested within the <data> element of a graph encloses any number of proper post-processor definitions, which comprise both the classname of the post-processor and the configuration for its parameters. The classname is given with the <y:Processor> element and the configuration is encoded using nested <y:Option> elements. XML attribute name of such a <y:Option> element denotes a correspondingly named option item, which is part of the OptionHandler object that is used to configure the post-processor's parameters.

As described above, a post-processor is invoked after the graph defined in the GraphML file has been parsed. If multiple <y:Processor> elements appear within a <y:Postprocessors> element, they constitute a post-processing chain where the actual invocation order of post-processors is determined by their order of appearance.

Example 10.12. Post-processor setup

<!-- A post-processor's setup nested within the <data> element of a graph. -->
<graph id="G" edgedefault="directed">
  <data key="d2">
    <y:Postprocessors>
      <y:Processor class="yext.graphml.processor.NodeSizeAdapter">
        <y:Option name="IGNORE_WIDTHS" value="false"/>
        <y:Option name="IGNORE_HEIGHTS" value="false"/>
        <y:Option name="ADAPT_TO_MAXIMUM_NODE" value="false"/>
      </y:Processor>
    </y:Postprocessors>
  </data>
  ...
</graph>

The tutorial demo application PostprocessorDemo.java demonstrates the post-processor functionality provided by the yFiles GraphML extension package. Note that the demo can also be used to conveniently generate proper XML markup for predefined post-processors from either the yFiles library or the yFiles GraphML extension package.

Tip

If the yFiles GraphML extension package is in the class path for command line tool GraphFormatConverter.java, it can be used, for example, to conveniently create GIF or JPG images from a given graph encoded in GraphML file format. Furthermore, if suitable post-processors are embedded in the GraphML file, the actual conversion that is performed by the tool can also automatically adjust the widths of all nodes or calculate a layout for the graph.

Tutorial Demo Code

Loading and saving graphs in GraphML file format is demonstrated in the tutorial demo application GraphMLDemo.java. CustomGraphMLDemo.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:

PostprocessorDemo.java demonstrates the post-processor functionality provided by the yFiles GraphML extension package.