GraphStream UI Tutorial 2 : Customising the display using style sheets

SourceForge.net Logo

In this tutorial we will look at more powerful way to customise the graph viewer. We will do this using a style sheet mechanism which is copied on the way CSS adds styling to HTML pages.

Note: the CSS found in GraphStream is naturally not the same as the one used for web pages, it only tries to copy it and to be as close as possible to its syntax and ideas.

A graph image

Style sheets

Up until now, we specified the colour and width and somewhat we called "edge-style" (to make edge appear as series of dots or dashes) of our graph elements using dedicated attributes. To change the colour of a node, for example, we used the ui.color attribute.

However this quickly becomes tedious if, for example, we want all nodes to be red. We have to do a loop on each node to set the ui.color attribute, and it becomes more and more complicated if we want more styling. Furthermore we have to do this for each new graph if we like red nodes.

The graph viewer provides far more styling capabilities than just colour and width, and these features are made accessible using style sheets. The GraphStream style sheets are copied on the cascading style sheets (CSS) used with HTML pages. The idea is to add style to elements of the graph instead of tags in an HTML page.

In GraphStream, a style sheet may look like this :

graph {
	color: red;
}
node {
	text-color: blue;
}
edge {
	width: 2;
}
		

Here is what you can get on a simple graph with the preceding style sheet :

A graph image

If you do not know CSS, this result may not be what you expected. Let see how it works. The style sheet is made of a collection of rules. A rule applies to one or more elements of the graph. The elements that match the rule are specified using selectors. The basic selectors are graph, node and edge. They mean that the rule will apply to the whole graph, only to all the nodes or only to all the edges, respectively.

The rule contents is then defined by putting styling elements or properties between braces just after the selector. Properties are pairs name-value separated by a colon. For example to specify a colour we write color: red. A rule may contain several properties, separated by semi-colons. For example color : red; text-color:blue;. Even if a rule contains only one property, the semi-colon is needed at the end.

The graph rule applies to all the elements of the graph. Therefore, specifying the colour in this rule will specify the colour for nodes and for edges, until the color property is also specified in the node or edge rules with a different value. This is the cascade. The style of the enclosing element (the graph) applies to the elements it contains (nodes and edges), until changed in the rule of these specific elements.

Therefore our graph appears all red, due to the first rule. If we had used the following style sheet :

graph {
	color: red;
}
node {
	text-color: blue;
}
edge {
	width: 2;
	color: green;
}

Our nodes would still have been red, but our edges would have been green (which is somewhat more artistic) :

A graph image

We can also refine the properties to concern only a specific graph element. Like tags in HTML that can have identifiers, each of our nodes and edges have an unique identifier. You can specify a rule for a node or edge knowing its identifier by using a selector of the form element#identifier. For example, to specify the style of the node whose identifier is "A", we can write :

graph {
	color: red;
}
node {
	text-color: blue;
}
edge {
	width: 2;
	color: green;
}
node#A {
	color: cyan;
}

Here we used node#A for our "A" node. The cascade works also for the rules that apply to specific elements. All node# rules inherit the node rule (that in turn, we have seen it above, inherits the graph rule). Therefore the text of the "A" node is still blue, but now this node is cyan :

A graph image

Now, we know the selector an rule syntax. But how do we attach style sheets to the graph ?

Specifying style

You can attach a style sheet only to a graph, not to nodes and edges, using the specific ui.stylesheet attribute. Here is an example :

import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui2.GraphViewerRemote;

public class TutorialUI002
{
	public static void main( String args[] ) {
		new TutorialUI002();
	}
	
	public TutorialUI002() {
		Graph graph = new DefaultGraph( false, true );

		GraphViewerRemote gvr = graph.display();
		
		gvr.setQuality( 4 );
		
		graph.addEdge( "AB", "A", "B" );
		graph.addEdge( "BC", "B", "C" );
		graph.addEdge( "CA", "C", "A" );
		
		graph.getNode("A").addAttribute( "label", "A" );
		graph.getNode("B").addAttribute( "label", "B" );
		graph.getNode("C").addAttribute( "label", "C" );

		// The important thing is here :
		
		graph.addAttribute( "ui.stylesheet", styleSheet1 );
	}
	
	public static final String styleSheet1 = 
		"graph {" +
		"    color:red;"+
 		"}" +
		"node {" +
		"    text-color:blue;" +
		"}" +
		"edge {" +
		"    width:2;" +
		"    color:green;" +
		"}" +
		"node#A {" +
		"    color:cyan;" +
		"}";
}

The example above yields the result we have already seen above :

A graph image

We have chosen here to incorporate the style sheet in the program as a character string. This is easier to publish here, however this is not very useful in real life.

You can put style sheets in separate text files and link them to a graph. This is done by using the same ui.stylesheet attribute but with an URL :

graph.addAttribute( "ui.stylesheet", "url('/some/path/to/your/stylesheet.css')" );

The url() marker tells the graph viewer your style sheet is not in the attribute string, but in a separate file given as argument. We have chosen to put the .css extension to the style sheet file name since the syntax is clearly CSS like and most CSS editors should be able to process it fine.

Now, it is easy to add the same style sheet to several different graphs. It is also easy to specify the styling, once and for all in the style sheet.

The style sheet as we have seen it above, can only be attached to a graph. You can also change the style of individual graph elements, however the process is a bit different. You do not attach a style sheet to individual elements, but you can change the style rule that applies to them. This is done using the ui.style attribute. In this case, you change or create a specific rule for the graph element you modify.

For example, for a node whose identifier is "A", specifying the style would change or create the style rule node#A. Here is an example that changes only the style of the A node :

import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.Node;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui2.GraphViewerRemote;

public class TutorialUI002b
{
	public static void main( String args[] ) {
		new TutorialUI002b();
	}
	
	public TutorialUI002b() {
		Graph graph = new DefaultGraph( false, true );

		GraphViewerRemote gvr = graph.display();
		
		gvr.setQuality( 4 );

		Node A = graph.addNode( "A" );
		Node B = graph.addNode( "B" );
		Node C = graph.addNode( "C" );
		
		graph.addEdge( "AB", "A", "B" );
		graph.addEdge( "BC", "B", "C" );
		graph.addEdge( "CA", "C", "A" );
		
		A.addAttribute( "label", "A" );
		B.addAttribute( "label", "B" );
		C.addAttribute( "label", "C" );

		A.addAttribute( "ui.style", "color:grey;text-color:black;border-width:1;border-color:black;" );
	}
}

An here is the result :

A graph image

Dynamic styling and classes

Naturally, most of the time you want the style to change according to the computation that is occurring on the graph. After all, the style is here to visualise data on the graph or an algorithm running on the graph.

The ui.width and ui.label and ui.color attributes are still available for changing the style. In fact these attributes are shortcut to access the styles of each node or edge. This is a quick, but limited, way to change the style of an element.

Then to change dynamically more style elements of a graph element the better way is to change the ui.style attribute. However this may not be easy since a ui.style attribute is a string containing several properties separated by semicolons. Changing one property would impose to send anew the whole style string to the graph viewer through attributes. For example changing the width, but conserving colour, text alignment, border width and border colour would force you to compose a new style string each time.

A better way is to use classes. A class is an attribute that defines categories of graph elements. You merely add the ui.class attribute to a node or edge with an arbitrary character string to make them pertain to the class defined by this string. It is then possible to assign styles to classes. Anew, it works the same way CSS uses classes.

Changing the style of an element is then a matter of changing its class, which is very simple to do. Here is an example of a style sheet that defines styles for a class named "active" :

node {
	color: yellow;
	text-color: black;
	border-width: 1;
	border-color: black;
}
node.active {
	color: red;
	text-color: yellow;
}

The classes are added to the selector after a dot. Here is a program that makes nodes change style dynamically, using ui.class attributes and the style sheet above (save it as "classesExample.css") :

import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.Node;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui2.GraphViewerRemote;

public class TutorialUI002b
{
	public static void main( String args[] ) {
		new TutorialUI002b();
	}
	
	public TutorialUI002b() {
		Graph graph = new DefaultGraph( false, true );

		GraphViewerRemote gvr = graph.display();
		
		gvr.setQuality( 4 );
		
		Node A = graph.addNode( "A" );
		Node B = graph.addNode( "B" );
		Node C = graph.addNode( "C" );
		
		graph.addEdge( "AB", "A", "B" );
		graph.addEdge( "BC", "B", "C" );
		graph.addEdge( "CA", "C", "A" );
		
		A.addAttribute( "label", "A" );
		B.addAttribute( "label", "B" );
		C.addAttribute( "label", "C" );
		
		graph.addAttribute( "ui.stylesheet", "url('classesExample.css');" );
		
		Node current = A;
		
		while( true )
		{
			current.addAttribute( "ui.class", "active" );
			
			try{ Thread.sleep( 700 ); } catch( Exception e ) {}
			
			current.removeAttribute( "ui.class" );
			
			if( current == A ) current = B;
			else if( current == B ) current = C;
			else if( current == C ) current = A;
		}
	}
}

As classes rules inherit the node rule, it is not necessary to define the border anew on the node.active rule. The example above changes the class of the three nodes one by one, making only one node "active" at a time :

A graph image

It is possible to specify several classes for a given element. Indeed an element can only have one identifier, however the number of classes is not constrained.

To specify several classes specify their names separated by comas as value of the ui.class attribute :

Node A = graph.getNode( "A" );
A.setAttribute( "ui.class", "class1,class2,class3" ); 

You can also specify the various classes using the fact setAttribute() can take a variable number of arguments :

Node A = graph.getNode( "A" );
A.setAttribute( "ui.class", "class1", "class2", class3" );

Cascading, inheritance and aggregation

The GraphStream CSS implementation is naturally different of the one you can find in Web navigators. Indeed navigators render web pages and GraphStream renders graphs which are quite different things !

Cascading

GraphStream also "cascade" the styles. This means that you can accumulate styles with some styles having precedence over others when there is a possible conflict.

The cascading is as follows : first a default style sheet is create automatically for you. This style sheet defines basic rendering style. Each time you specify a style sheet as an attribute to the graph, this new style sheet takes precedence to the default style sheet and override and complete it.

The last level of cascading is provided by the possibility to change the style of individual elements by using the ui.style attribute or the ui.color, ui.width, etc. attributes. These last elements take precedence over all other styles.

Naturally, order matters, and for competing styles, if a style is found after another the last one is supersede the first one. This is important most notably when an element has several classes. The order in which you list the classes is important and the last defined one supersede the others.

When a style supersede another, the conflicting properties are replaced by the superseding style. If a property is only in the superseded style it remains (it is not removed) in the element style. If the property is only in the superseding style, it is added to the element style.

Inheritance

As it was explained above, styles can inherit one another. In GraphStream, inheritance is limited since there are few elements that contain others. The only inheritance is from graphs to graph elements : All styles inherit the graph style.

Aggregation

The style of each element may be the aggregate of several style rules in the style sheet. This happens every time an element has both a style given for its identifier and a style given by the class it pertains to. For example :

node#A { color: red; }
node.foo { width: 20px; }

In this example if a node with identifier "A" and a ui.class attribute whose value is foo exists it will both be red and have a width of 20 pixels.

In other words, styling rules with identifiers and styling class rules do not conflict, they aggregate.

If an element pertains to several classes, the classes also aggregate.

Units

When you position your nodes by yourself, you specify coordinates of your choice. The graph viewer adapts its view so that each node is visible. When the automatic layout is used the same process occurs. The node positions are computed by an algorithm, and then the viewer adapts the view so that the graph aspect ratio and dimensions best fill the display.

Most of the time you render the graph on screen, and there is a conversion from the units used for the node coordinates toward the units used for drawing. Most graph viewers will use pixels as units.

This conversion is transparent, but starts to become important as soon as you need to specify lengths, widths and heights for graphical elements drawn by the viewer. Let's call the units you use for specifying node coordinates "graph units" or "gu" and units used to draw the view "pixels" or "px". If you specify the various lengths in graph units (like for example arrow width, node width, etc.), their apparent size will vary according to the zooming done automatically by the graph viewer to fit the whole graph display into the view. Therefore lengths will seem to vary.

To avoid this, by default, all lengths used in the style sheets are expressed in pixels. This means that they will always occupy the same space on screen whatever the size of the graph viewer window, or the size of the graph. The only thing that may make them smaller is the resolution and size of your screen (that hopefully does not dynamically change often).

However, it is possible to choose the units in which lengths are expressed in the style sheet. You do this by appending a suffix to all lengths. At this time you can use three suffixes: px for pixels (the default, thus), gu for graph units and % for percents. Percents are expressed as a percent of the viewer width (or sometime height, depending on the property)).

Here is an example of style sheet that puts an image on the graph itself to draw a background. The coordinates are given in graph units to make the image fit a predefined area, and the nodes are also positioned without automatic layout:

import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.Node;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui.GraphViewerRemote;

public class TutorialUI002d {
	public static void main( String args[] ) {
		new TutorialUI002d();
	}
	
	public TutorialUI002d() {
		Graph graph = new DefaultGraph( false, true );
		
		GraphViewerRemote viewerRemote = graph.display( false );
		
		viewerRemote.setQuality( 3 );
		
		Node A = graph.addNode( "A" );
		Node B = graph.addNode( "B" );
		Node C = graph.addNode( "C" );
		Node D = graph.addNode( "D" );
		
		graph.addEdge( "AB", "A", "B", true );
		graph.addEdge( "BC", "B", "C", true );
		graph.addEdge( "CA", "C", "A", false );
		graph.addEdge( "AD", "A", "D", true );
		graph.addEdge( "DB", "D", "B", false );
		
		A.addAttribute( "x", 0 );
		A.addAttribute( "y", 1 );
		B.addAttribute( "x", -1 );
		B.addAttribute( "y", 0 );
		C.addAttribute( "x", 1 );
		C.addAttribute( "y", 0 );
		D.addAttribute( "x", 0 );
		D.addAttribute( "y", -1 );
		
		graph.addAttribute( "ui.stylesheet", styleSheet1 );
	}
	
	protected static final String styleSheet1 =
		  "graph {" +
		  "		image:url('http://www.iconarchive.com/icons/flameia/aqua-smiles/make-fun-128x128.png');" +
		  "		image-offset: -1gu -1gu;" +
		  " 	width: 2gu;" +
		  "		height: 2gu;" +
		  "}" +
		  "node {" +
		  "		width: 10%;" +
		  "		height: 10%;" +
		  "		node-shape: image;" +
		  "		text-align: aside;" +
		  "		border-width: 1px;" +
		  "		border-color: red;" +
		  "}" +
		  "edge {" +
		  "		color:#FF000055;" +
		  "}";
	
}

This tutorial demonstrates the use of gu units and % units. The image is put on the background at -1 graph units in X and Y. We use the width and height properties to change the size of the image to 2 (which works well on graphs). As we put ourselves the nodes at coordinates between -1 and 1 and make the image runs between these coordinates, it appears centered under the nodes and edges.

We used percents to define the node width and heights. These percents express a fraction of the overall viewer width. Therefore the nodes will grow or shrink with the viewer window (try resize the window to see it).

A graph image

Some of the most used style properties

Here is a (non-exhaustive) list of some styling elements. Some properties are dedicated to a nodes or edges, others are general.

For a more complete list of style properties, with examples, see this tutorial.

List of general styling elements

color
This specifies the "overall" colour of the element. For nodes this is the background, for edge this is the edge colour. You can specify colour using HTML names or rgb/rgba CSS specifications, for example red would be rgb(255,0,0), green would be rgb(0,255,0 and blue rgb(0,0,255). To specify a color with transparency you can use rgba(0,0,255,128) (semi-transparent blue). You can also specify colours using the #FFFFFF notation. The value can be more than one colour. This allows to specify gradients for example. Simply put several colour values separated by spaces.
background-color
The colour of the background. This actually is used only when put on graphs, but some node and edge shapes may use it in the future. This work the same as the foreground colour.
width
The width in pixels of the graph element. For nodes this specifies the diameter or overall width, for edges it is the "heaviness".
border-width
The width of the border. By default this is set to zero.
border-color
The borders colour.
text-font
The font to use for node and edge labels.
text-size
The size in points of the text labels.
text-color
The color of the text labels.
text-align
The text alignment with respect to the graph element centre. There are five possibles values : left, right, center, aside, along and hidden. The aside value put the text on the right of the graph element but takes care of the width and avoid to put the text on the element (the right value puts the text just at right of the element center). The along value works only for edges. It orients the text along the edge vector, making it follow the edge slope.
text-style
Specifies the font style, you have four possible values, plain, bold, italic and bold-italic.
text-mode
The text mode has three values : normal, truncated and hidden. The truncated value renders the label at with a limit of twenty characters. The hidden value allows to completely hide the text.
z-index
The z-index allows to specify a drawing order. By default all edges are draw at z-index 1, and all nodes at z-index 2. This means that nodes are drawn above edges. You have z-index values between -127 to 127 to put elements under or above others (there is a default third layer above nodes that draw sprites, but this subject is discussed in another tutorial).
image
Specify an image attached to the graph element. This is an URL. To specify the image you can write url('/path/to/an/image') for example. This styling element does not display the image proper, it only tells where is the image. To display the image you have to specify a shape for nodes or edges (see under).
shadow-style
Specify the kind of shadow to draw for elements. Actually two values are available none and simple. The simple shadows are uniform coloured shadows that take the shape of their element.
shadow-offset
This must be followed by two numbers separated by a space. This allows to offset the shadow along the X and Y axes.
shadow-color
The shadow colour. Like for other colours, several values can be specified. This will allow to implement soft shadows, later.
shadow-width
Allow to graw the shadow compared to the shape of the element. This is a number that adds to the element width.

Properties for nodes only

node-shape
The node shape define the look of the node. You have seven possibilities: circle (the default), square, triangle, cross, image, text-box and text-ellipse. The circle, square, cross and triangle shapes are simple shapes that are helper to style elements for black and white rendering for example. The image shape use the image styling element defined above. The text-box and text-ellipse styles allow to draw a box or ellipse around the text label of the node. If an image is specified, the image is drawn aside the label.

Properties for edges only

edge-shape
The shape defines the look of the edge. You have three choices : line, angle, and cubic-curve. The first one is the default. It draws the edge as a straight line. Varying the width plays in the thickness of the line. The angle shape draws the edge as a sort of triangle whose base is given by the width. The cubic-curve shape draws edges whose connections with nodes is always directed in cardinal directions (north, east, south, west) and drawn with a curve between the two connected nodes.
edge-style
The style defines three values: plain, dots and dashes.
arrow-shape
The arrow shape defines the look of arrows when edges are directed. You have five possibilities none, simple, simple diamond, circle, and image. The setting "none" allows to never draw an arrow even if the edge is directed. The "simple" setting draws a conventional arrow. The "diamond" setting draws a rhombus shape. The image shape draws an image specified with "arrow-image" (see under).
arrow-image
This specifies the image used in place of arrows when the arrow-shape is image.
arrow-length
The length of the arrow.
arrow-width
The thickness of the arrow.

Properties for sprites only

Sprites are explained later in the tutorials, however for completeness, their major styling properties are given here.

sprite-shape
Sprites share some shapes with nodes, but add new ones.
sprite-orientation
As sprites can orbit around nodes and edges, they can point to various directions.

Some screen shots

Here are some graph renderings that can be achieved with GraphStream styling :

A graph image

A graph image

A graph image

A graph image

A graph image

css xhtml