Transfer of additional information between server and client

Tips & Tricks

Summary

This article shows how to transfer additional data of simple and complex type mapped to items as edges, nodes, graph, etc. between server and client.

Description

It is often necessary to transfer data which is not part of the default implementation between server and client, additional information to influence the layout or the visual representation of the graph and its elements, for instance.
The necessary steps differ slightly whether you want to add simple data types (like String, int, long, float, double or bool) or complex objects.

Server (yFiles Java)

Since yFiles FLEX 1.5 the integrated GraphML support of yFiles for Java 2.7 is used instead of the GraphML extension package. As some of the relevant method signatures changed both versions will be explained and marked either as 'since yFiles FLEX 1.5' for the usage of the integrated GraphML or as 'until yFiles FLEX 1.4' for the usage of the GraphML extension package.

Transferring simple attributes

On the server side, a simple attribute has to be registered for reading and writing with the
com.yworks.yfiles.server.graphml.support.GraphRoundtripSupport, using:

since yFiles FLEX 1.5

GraphRoundtripSupport.addMapper(Object tag, String name, KeyType valueType, KeyScope scope);
With the parameters:

until yFiles FLEX 1.4

GraphRoundtripSupport.addMapper(Object tag, String name, int graphMLType, int graphMLScope);
With the parameters:
  • tag: the provider Key to get the DataMap from the graph using Graph.getDataProvider(Object providerKey)
  • name: the name for the data used in the GraphML file
  • graphMLType: an integer constant describing the type of the attribute, as defined in
    org.graphdrawing.graphml.attr.AttributeConstants
  • graphMLScope: an integer constant describing the scope (e.g. node, edge), as defined in
    org.graphdrawing.graphml.GraphMLConstants
GraphRoundtripSupport grs = new GraphRoundtripSupport(); // create a mapper with the tag "edgeProviderKey" // which will write int data for edges into attributes named "edgeIndex" // since yFiles FLEX 1.5: grs.addMapper("edgeProviderKey", "edgeIndex", KeyType.INT, KeyScope.EDGE); // until yFiles FLEX 1.4: // grs.addMapper("edgeProviderKey", "edgeIndex", AttributeConstants.TYPE_INT, GraphMLConstants.SCOPE_EDGE); // create another mapper which is used to map data to the graph itself // since yFiles FLEX 1.5: grs.addMapper("graphProviderKey", "graph-data", KeyType.STRING, KeyScope.GRAPH); // until yFiles FLEX 1.4: //grs.addMapper("graphProviderKey", "graph-data", AttributeConstants.TYPE_STRING, GraphMLConstants.SCOPE_GRAPH); // get the LayoutGraph created by the GraphRoundtripSupport LayoutGraph graph = grs.createRoundtripGraph(); // get the DataProvider with the tag "edgeProviderKey" DataMap dp = (DataMap) graph.getDataProvider("edgeProviderKey"); // fill the Map with edge related data (here as example the index of the edge) for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) { Edge edge = ec.edge(); dp.setInt(edge, edge.index()); } // get the DataProvider with the tag "graphProviderKey" DataMap graphDp = (DataMap) graph.getDataProvider("graphProviderKey"); // map the data to the graph graphDp.set(graph, "I'm the graph!"); // send the graph to the client grs.sendGraph(graph, httpServletResponse);

Transferring complex attributes

A complex attribute on the other hand has to be registered using:

since yFiles FLEX 1.5

GraphRoundtripSupport.addObjectMapper(Object tag, KeyScope scope, SerializationHandler serializer, DeserializationHandler deserializer)
With the parameters:
  • tag: the provider key to get the DataMap from the graph using Graph.getDataProvider(Object providerKey)
  • scope: a KeyScope describing the scope (e.g. node, edge)
  • serializer: The serializer instance that will be used for writing the complex data objects.
  • deserializer: The deserializer instance that will be used for reading the complex data objects.

until yFiles FLEX 1.4

GraphRoundtripSupport.addObjectMapper(Object tag, int graphMLScope, ISerializer serializer, IDeserializer deserializer);
With the parameters:
  • tag: the provider Key to get the DataMap from the graph using Graph.getDataProvider(Object providerKey)
  • graphMLScope: an integer constant describing the scope (e.g. node, edge), as defined in
    org.graphdrawing.graphml.GraphMLConstants
  • serializer: The serializer instance that will be used for writing the complex data objects.
  • deserializer: The deserializer instance that will be used for reading the complex data objects.
GraphRoundtripSupport grs = new GraphRoundtripSupport(); // create a mapper with the tag "nodeObjectProviderKey" // which will write complex data of type IBPElement for nodes into attributes using the same name // since yFiles FLEX 1.5 support.addObjectMapper("nodeObjectProviderKey", KeyScope.NODE, new BPSerializer(), new BPDeserializer()); // until yFiles FLEX 1.4 // support.addObjectMapper("nodeObjectProviderKey", GraphMLConstants.SCOPE_NODE, new BPSerializer(), new BPDeserializer()); // get the LayoutGraph created by the GraphRoundtripSupport LayoutGraph graph = grs.createRoundtripGraph(); // get the DataProvider with the tag "nodeObjectProviderKey" DataMap dp = (DataMap) graph.getDataProvider("nodeObjectProviderKey"); // fill the Map with node related data (here as example new BPActivity objects) for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) { Node node = nc.node(); dp.set(node, new BPActivity()); } // send the graph to the client grs.sendGraph(graph, httpServletResponse);

Server (yFiles.NET)

Transferring simple attributes

On the server side, the simple attribute has to be registered for reading and writing with the yWorks.yFiles.Graph.Web.GraphRoundtripSupport, using
  • GraphRoundtripSupport.AddMapper<K, V>(object tag, string name) or
  • GraphRoundtripSupport.AddMapper<K, V>(object tag, string name, KeyScope scopeType, KeyType keyType)

The type argument K corresponds to the type to store the attribute for (mostly INode or IEdge). The type argument V corresponds to the type of the values to be stored (mostly string). The following parameters are required:
  • tag: The tag that is to be used to retrieve the mapper from the graph's mapper registry
  • name: The name of the attribute that is to be used for GraphML I/O
The scopeType and keyType parameters can either be passed explicitly or they will be inferred from the type attributes K and V. The following example registers a custom mapper for edge attributes, sets the mapper values and sends the graph to the client. GraphRoundtripSupport roundtripSupport = createRoundtripSupport(); // create a mapper with the tag "edgeProviderKey" // that will write int data for edges into attributes named "edgeIndex" roundtripSupport.AddMapper<IEdge,string>("edgeProviderKey", "edgeIndex"); // create a mapper to map data to the graph itself roundtripSupport.AddMapper<IGraph, string>("graphProviderKey", "graph-data"); // get the properly configured IGraph created by the GraphRoundtripSupport IGraph graph = roundtripSupport.CreateRoundtripGraph(); // retrieve the IMapper instance and register some data IMapperRegistry registry = graph.Lookup(typeof(IMapperRegistry)) as IMapperRegistry; if (null != registry) { IMapper<IEdge, string> edgeIndexMapper = registry.GetMapper<IEdge,string>("edgeProviderKey"); if( null != edgeIndexMapper ) { int i = 0; foreach( IEdge edge in graph.Edges ) { edgeIndexMapper.SetValue( edge, i++.ToString() ); } } IMapper<IGraph, string> graphMapper = registry.GetMapper<IGraph,string>("graphProviderKey"); graphMapper.SetValue( graph, "I'm the graph!" ); } // send the graph to the client roundtripSupport.SendGraph(graph,context.Response);

Transferring complex attributes

A complex attribute has to be registered for reading and writing with the yWorks.yFiles.Graph.Web.GraphRoundtripSupport using GraphRoundtripSupport.AddObjectMapper<K, V>(object tag, KeyScope scopeType, ISerializer serializer, IDeserializer deserializer ).

The type arguments K and V again correspond to the type to store the attribute for resp. the type of the values to be stored. The following parameters are required:
  • tag:The tag that is to be used for the graph's mapper registry.
  • scopeType: The graphml scope of the data.
  • serializer: A serializer instance that will be used for writing the attribute objects.
  • deserializer: A deserializer instance that will be used for parsing the attribute objects.
The following example registers a custom mapper for complex node attributes, sets the mapper values and sends the graph to the client. GraphRoundtripSupport roundtripSupport = createRoundtripSupport(); // create a mapper with the tag "nodeObjectProviderKey" // that will write complex data of type CustomDataObject for nodes. roundtripSupport.AddObjectMapper<INode, CustomDataObject>("nodeObjectProviderKey", KeyScope.Node, CustomDataObjectSerializer.instance, CustomDataObjectDeserializer.instance); // get the properly configured IGraph created by the GraphRoundtripSupport IGraph graph = roundtripSupport.CreateRoundtripGraph(); // retrieve the IMapper instance and register some data IMapperRegistry registry = graph.Lookup(typeof(IMapperRegistry)) as IMapperRegistry; if (null != registry) { IMappe<INode, CustomDataObject> nodeDataMapper = registry.GetMapper<INode, CustomDataObject>("nodeObjectProviderKey"); if( null != nodeDataMapper ) { int i = 0; foreach( INode node in graph.Nodes ) { nodeDataMapper.SetValue( node, new CustomDataObject(++i); } } } // send the graph to the client roundtripSupport.SendGraph(graph,context.Response);

Client

A GraphML file containing custom mapped data will look like this: &lt;graphml> ... <!-- The custom GraphML attribute --> <key id="d0" for="edge" attr.name="edgeIndex" attr.type="int"/> <key id="d1" for="graph" attr.name="graph-data" attr.type="string"/> <graph id="G" edgedefault="directed"> ... <!-- An edge that has a <data> element referring to the GraphML attribute "d0" --> <edge id="e0"> ... <data key="d0">0</data> <!-- The transferred value is 0 --> </edge> ... <data key="d1">I'm the graph!</data> <!-- This data is mapped to the graph --> </graph> </graphml>

Handling simple attributes

On the client side, the simple attribute has to be registered with the RoundtripHandler, using
RoundtripHandler.addMapperAttribute(tag:Object, name:String = null, scope:String = GraphMLConstants.SCOPE_NODE, contentType:String = GraphMLConstants.TYPE_STRING). The parameters are the same as on the server side:

  • tag: the provider Key
  • name: the name for the data used in the GraphML file
  • scope: a String constant describing the scope (e.g. node, edge), as defined in com.yworks.io.graphml.GraphMLConstants
  • contentType: a String constant describing the type of the attribute, as defined in com.yworks.io.graphml.GraphMLConstants

The corresponding mapper has to be registered with the graph's mapper registry before the graphml data is read into the graph instance.

The following code reads the above GraphML file: var graph:DefaultGraph = DefaultGraph(graphCanvas.graph); // register the mapper for the attribute to the graph graph.mapperRegistry.addMapper( "clientEdgeProviderKey", new DictionaryMapper(true) ); graph.mapperRegistry.addMapper( "clientGraphProviderKey", new DictionaryMapper(true) ); var roundtripHandler:RoundtripHandler = new RoundtripHandler(graphCanvas); // register the mapper for the attribute to the RoundtripHandler roundtripHandler.addMapperAttribute( "clientEdgeProviderKey", "edgeIndex", GraphMLConstants.SCOPE_EDGE, GraphMLConstants.TYPE_INT); // do the same for data which is mapped to the graph roundtripHandler.addMapperAttribute( "clientGraphProviderKey", "graph-data", GraphMLConstants.SCOPE_GRAPH, GraphMLConstants.TYPE_STRING); ... // let the roundtripHandler read the data here ... // get the mapper var dataMapper:IMapper = graphCanvas.graph.mapperRegistry.getMapper( "clientEdgeProviderKey" ); // read the data from the mapper var mappedEdgeData:int = dataMapper.lookupValue(anEdge); // the same for the graph var graphMapper:IMapper = graphCanvas.graph.mapperRegistry.getMapper( "clientGraphProviderKey" ); var mappedGraphData:String = graphMapper.lookupValue(graphCanvas.graph);

Handling complex attributes

If using a yFiles FLEX version prior to 1.3.1 the RoundtripHandler can only handle simple attributes, as String, long, int, float, double and boolean. In order to transfer objects, one has to implement his own input handler (see yFiles FLEX Developer's guide, Chapter 3, Customizing the yFiles FLEX I/O Support). Since yFiles FLEX 1.3.1 you can add complex attributes using the RoundTripHandler's addObjectMapperAttribute method which is explained in this article.

A complex attribute has to be registered on client side with the RoundtripHandler, using
RoundtripHandler.addObjectMapperAttribute(tag:Object, scope:String, serializer:ISerializer, deserializer:IDeserializer ).
Again the parameters are the same as on the server side:

  • tag:The attribute tag. The attribute mapper has to be registered in the graph's mapper registry with this tag. A string representation of the tag will also be used as the attribute name for the GraphML attribute.
  • scope: The scope type of the attribute. One of
    • GraphMLConstants.SCOPE_NODE
    • GraphMLConstants.SCOPE_EDGE
    • GraphMLConstants.SCOPE_PORT
    • GraphMLConstants.SCOPE_GRAPH
    • GraphMLConstants.SCOPE_GRAPHML
    • GraphMLConstants.SCOPE_ALL
  • serializer: The serializer instance that will be used for writing the attribute objects.
  • deserializer: The deserializer instance that will be used for reading the attribute objects.

The corresponding mapper has to be registered with the graph's mapper registry before the graphml data is read into the graph instance.

The following code reads a transferred complex node attribute:

var graph:DefaultGraph = DefaultGraph(graphCanvas.graph);

// register the mapper for the attribute to the graph
graph.mapperRegistry.addMapper( "nodeObjectProviderKey", new DictionaryMapper() );

var roundtripHandler:RoundtripHandler = new RoundtripHandler(graphCanvas);

// register the mapper for the attribute to the RoundtripHandler
roundtripHandler.addObjectMapperAttribute("nodeObjectProviderKey", GraphMLConstants.SCOPE_NODE, 
new BPSerializer(), new BPDeserializer());
        ...
// let the roundtripHandler read the data here
        ...
// get the mapper                        
var dataMapper:IMapper = DefaultGraph( graphCanvas.graph ).
         mapperRegistry.getMapper( "nodeObjectProviderKey" );

// read the data from the mapper
var mappedNodeData:Object = dataMapper.lookupValue(aNode);

Please note that the signature of the addMapper / addMapperAttribute methods differs between the different implementations:

  • Java (since yFiles FLEX 1.5): tag, name, type, scope; type is a KeyType, scope is a KeyScope
  • Java (until yFiles FLEX 1.4): tag, name, type, scope; type and scope are int constants, type is defined in AttributeConstants, scope defined in GraphMLConstants
  • Flex: tag, name, scope, type; scope and type are int constants, both are defined in GraphMLConstants
  • .NET: tag, name, scope, type; scope and type are defined in the KeyScope and KeyType enumerations

The same mechanism also works for sending from the client to the server.

As shown in the examples you can also send data mapped to your graph instance.
This works exactly as demonstrated with nodes and edges, just make sure to use the graph scope when registering the mappers for the roundtrip.

Tha data is mapped using the graph instance as key.


See also yFiles FLEX Developer's guide, Chapter 3, Customizing the yFiles FLEX I/O Support.

Categories this article belongs to:
yFiles FLEX > yFiles FLEX Java Server API > Roundtrip Support Classes
yFiles FLEX > Input and Output > Working with the GraphML File Format
yFiles FLEX > Communicating with the Server > Remote Communication in yFiles FLEX
yFiles FLEX > yFiles FLEX .NET Server API > Class GraphRoundtripSupport
Applies to:
yFiles FLEX: 1.4, 1.5, 1.6, 1.7, 1.8
Keywords:
Client - Server - Communication - GraphML - Attributes - AttributeMapper