All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.ui.graphicGraph.HowItWorks.mkd Maven / Gradle / Ivy

How the graphic graph works
===========================

This document describes the way the graphicGraph package is done. It does not describe the way the styles and style sheet is implemented, this is documented in another file inside the `stylesheet` subpackage.

This is a technical document that explains how the graphic graph package works so that you can use it when designing viewers or other graphic outputs with GraphStream.

The `graphicGraph` package defines a `Graph` implementation whose purpose is to provide a graph representation that helps in rendering graphs on screen or on other medias. It is composed of a set of classes that allow to store a graph with graphic attributes. These classes are :

* `GraphicGraph`: A graph made of graphic nodes, edges and a new kind of elements named sprites plus their styles.
* `GraphicElement`: A base class for graphic nodes, edges and sprites.
	* `GraphicNode`: A node representation.
	* `GraphicEdge`: An edge representation.
	* `GraphicSprite`: A new entity called sprite that can be rendered anywhere on the graph.
* `StyleGroup`: A group of graphic graph elements sharing the same graphic style.
* `StyleGroupSet`: All the style groups as well as all the graphic nodes, edges and sprites, this is the base for the `GraphicGraph` implementation.
* `StyleGroupListener`: A listener on the style group set to know when a style changes.
	
Basically, a graphic graph can be seen as the sum of three elements:

* A style sheet (`StyleSheet`) that defines graphic styles.
* A set of style groups (`StyleGroupSet`) that define which graph elements maps to which styles.
* A connectivity that allows to know which node it tied to which other one.

``An important note``: The `GraphicGraph` class is *NOT* a general purpose graph! It is not even intented to be used as a graph, but only as an easy way to represent the graph data needed to display a graph. Although it implements the `Graph` interface, it would perform poorly in almost all usual tasks requiring a general purpose graph. The data structures in the `graph` package are far more optimized to this end. However, the `GraphicGraph` is fairly optimized for graph drawing. It allows to process the graph elements in their Z order, style group by style group. It allows to easily be notified when important UI attributes change (label, position, color, size). It defines specific `Elements` classes like `GraphicNode`, `GraphicEdge` and the very special `GraphicSprite`. The reason `GraphicGraph` implements `Graph` is that it allows it to be a Sink for a general graph, and that it is far more elegant to see it as a `Graph` in most situations, however, internally, it is not stored as a graph but as a set of style groups, stored in Z order, with additional connectivity information.

The graphic graph, graphs and threads
-------------------------------------

The purpose of the graphic graph is twofold :

* To represent a graph with style and sprites and allow fast rendering.
* To be the some sort of copy of another graph, to avoid accesses from a viewer in the Swing thread to a graph that should then be completely synchronised (and would therefore prevent any gain of using several threads and CPU cores).

The first item is self-explanatory: the graphic graph role is to help in drawing a graph by
providing style with style sheets and helpers to position an retrieve graph elements in space.

The second item deserves a more complete explanation.

Most of the time, drawing in Java is done through the Swing thread. Drawing in the Swing
thread means that the main thread that is created when any Java application starts is not
used. If it is used, one must ensure a good synchronisation between the main thread an
the Swing thread.

In GraphStream, most of the work is often done in the main thread. When one wants to display
a graph, the Swing thread is used. Then several scenarios could occur:

1. You want to design a graphic application with buttons, menus etc. and one or more graph graphical representations. In this case, the whole application can be done inside the Swing thread and the main thread can be left alone.
2. You want to run one or more algorithms on the graph in the main thread and use the display capabilities to monitor them. In this case the application is split in two threads, the main thread that runs the algorithms on a graph implementation, and the Swing thread that displays the graph.
3. In any of the case above, you want the computation (an algorithm, a simulation) on the graph to occur in a dedicated thread that will not be interrupted by display or user events (buttons, clicks, etc. (although it can catch them !)).
	  
In all cases excepted the first, you have two threads, with one containing a standard graph implementation, and another doing the display of this graph. Lets call them the main thread and the Swing thread respectively.

We have chosen to duplicate the standard graph into what we call a graphic graph so that the standard graph can be edited freely in the main thread, and the graphic graph can be consulted freely in the Swing thread. Between the two a `ThreadProxyFilter` is used to send events from the main graph to the graphic graph, passing the thread frontier. The proxy acts as a mail box. When an event occurs in the main graph, the event is sent in the mail box. When the Swing thread has time to do it, it consults its mail box to update its graph representation. Only the mail box has to be synchronised for this to work.

But why duplicate the graph ? We could merely synchronise the graph in the main thread to render it in the Swing thread. This would be a bad idea. The reason is that the display accesses the graph constantly. In some real-time renderers, the display refreshes at 24 frames per second (or more !). This means it accesses the graph 24 times per second. If we were using synchronisation, we would be locking the main graph 24 times per second, leaving a few moments only to the main thread to read and update the graph, which is also its main purpose. In other words, the benefits of having two threads would be null, we would sequentialize things with no thread working in parallel since their critical section, the shared section, the graph, would be locked every time.

Instead we have chosen to consume a little more memory (you will see that the graphic graph tries to minimise its memory consumption, and is indeed not a complete copy of the main graph, but only the data needed to render it, for example, it does not store all attributes of the main graph), by storing a copy of the main graph as a graphic graph, and use the thread proxy mechanism to copy the main graph to the Swing graph. The mail box principle is a very thin layer between the two threads will little overhead. In addition it can be exchanged to a network proxy mechanism, allowing to put the graph computation on one machine and the display on a distant machine, which is an overly cool concept :-).

How the graphic Graph works
---------------------------

As you can see, there is no direct storage for nodes, edges and sprites as this can be retrieved easily from the `StyleGroupSet`. The only information really stored directly inside the graphic graph is the connectivity information, that is which node is linked to which other nodes by edges.

### Graph as a filter
	
The graphic graph and all its graphic elements (including sprites) store attributes like any other graph element in GraphStream. However it stores only a subset of such attributes. Basically, it stores only:

* all attributes starting with "ui.",
* attributes "x", "y", "z", "xy", "xyz",
* attribute "label", although "ui.label" is preferred,
* attribute "stylesheet", although "ui.stylesheet" is preferred.
	
Some of these attributes are directly converted to object attributes (variables of an instance) of the graphic graph elements. All styles are parsed automatically and update the style sheet.

The graphic graph, as all graphs in GraphStream is a `Filter`, that is something that can serve as sink or source. Most of the time the graphic graph is a sink, a copy of another graph that is used to do the real work on the graph. The graphic graph copies it for the purpose of displaying the main graph.
	
But the graphic graph can also be a source, that is something that sends events. You can put elements or attributes sinks on it. However due to the attribute filtering feature, most attributes will not pass through the graphic graph.
	
### Graph synchronisation
	
The fact the graphic graph is a filter allows to do such a thing: You have a main graph that you are working on, and you have a graphic graph (maybe in another thread) that is listening at the main graph. But to get back the modifications done by the viewer (moving a node for example), you make the main graph a listener of the graphic graph. You end up with a loop like this :
	
	 +--->main graph ---listens-at---> graphic graph---+
	 |                                                 |
	 +------------------listens-at---------------------+
		 
This could lead to nasty recursive calls. For example imagine that you change an attribute "ui.foo" in the main graph. You expect the graphic graph to get the modification since it listens at the main graph. This is what will happen. But by modifying itself, the graphic graph will automatically generate an attribute changed event to its listeners : that is toward the main graph that will generate an attribute changed event toward the graphic graph, etc. etc.
	
However such a loop is really helpful. We call this "graph synchronisation" since the idea is to propagate any change from the main graph to the graphic graph and any change from the graphic graph to the main graph. This is useful because all the changes you will do in the main graph will appear in the viewer since the graphic graph changed. But any change you do with the mouse in the graphic graph can be propagated in the main graph also.
	
We avoid the infinite loop using a system of time tokens and source name for events. But graph synchronization is a more general talk and is discussed in the more general GraphStream documentation.

The synchronization mechanism, with the GraphicGraph, is used to get back informations like the position of nodes (if this is not computed in the main thread), the mouse clicks on nodes, edges and sprites, the selection of nodes, etc.

How does the style sheet and styles communicate with the graphic elements
-------------------------------------------------------------------------

The binding between styles and elements is done by events.

When an element is added to the graphic graph, its style is automatically checked from the style sheet. The way a style binds to an element is complicated and depends on rules defined in the style sheet. The `StyleGroupSet` handles this complex process automatically. You can have a look at the `HowItWorks.mkd` document in the `stylesheet` package in order to understand how the binding is done.

As some elements may have the same style, they are put in the same style groups.

When a style change the style groups are automatically updated, some elements may change of group in the process. Once again, the style group set handles all this process automatically. All you have to know is that all the elements will always be assigned a style group. Inside the style group, all elements have the same style. When one wants to display a graph is can be very beneficial to process elements by groups. This way you can setup the style once, and render all elements in bulk operations. For example, using swing, you setup the `Graphics2D` (color, stroke, fill, font, etc.) only once then draw all elements. When using OpenGL you setup a shader only once, then draw all elements at once. etc.

How the sprites work
--------------------

Sprites are little graphical elements that can be added in a display of a graph to represent data stored in the graph. Sprites can be attached to nodes or edges or float freely in the graph representation area. Several sprites can be attached to one node, to one edge or to the graph. Sprites can have a style, in the very same way nodes, edges and graph can have one. You should read the general GraphStream documentation on sprite to better understand how they are attached to nodes and edges, and how they are positioned according to their attachment.

Sprites can move along edges, rotate around nodes or move freely in the graph display. They can change in appearance at any time. Therefore not only are they able to represent data, but they also can represent data dynamics.

Sprites are not part of an usual graph definition. In the graphic graph implementation however, sprites are elements of the graph as are nodes or edges. How you view sprites is different when you are a user of the Graph interface or if you try to understand how the graphic graph works.

Lets see the sprites as seen from the user perspective.

### For the user

For the user of the Graph class (not the graphic graph!) there are no sprites. It would make no sense to define sprites when they have no meaning in graph theory. Therefore another package in the `ui` branch, named `spriteManager`, provides a handler for sprites. A sprite manager is associated to a given graph.
	
The sprite manager allows to associate sprites to graphs, nodes and edges. It allows to move them, attach them to (or detach them from) graph elements, change their style, or their contents, etc. The sprite manager contains `Sprite` objects that you can manipulate with appropriate methods.

Now lets see how the sprites are handled really.

### For the GraphStream developer

In fact, in the `Graph`, the sprites are handled by attributes. Each time a sprite is created by the `SpriteManager`, special attributes are updated automatically in the corresponding graph. Each time a sprite is changed, the attributes are updated.
	
When the sprite manager is first associated to a graph, it browses it to know if there already are some sprites in it and updates itself accordingly to provide corresponding `Sprite` instances. If the graph is read from a file format that supports attributes, the sprites are therefore restored transparently. This works the same if a graph is written on disk.
	
This mechanism also works with the rest of the input output system. As sprites are in fact attributes, they are sent via the input output mechanism to all listeners. This is the mechanism used by the graphic graph to know how many sprites there is in the graph and their properties.
	
As seen above, the graphic graph is a copy of the main graph. However in the graphic graph, sprites are real elements of the graph, mainly because it is far more easy to handle them this way for rendering. They are created and updated automatically when attributes change. Indeed, in the graphic graph, the elements are:

* `GraphicNode`
* `GraphicEdge`
* `GraphicSprite`

These three elements are descendant of `GraphicElement` which in turns implement `AbstractElement`, and `Element`, the basis for all parts of a graph in GraphStream.
	
### Sprites as attributes of the graph

As in regular graphs the sprites are stored as attributes, and as a sprite is described by several properties. Therefore we have two possibilities to describe a sprite:

* Represent a sprite as an object stored as one attribute in the graph.
* Represent a sprite as several attributes, each attribute representing a property of the sprite.
	
This may seem unnatural, but we have chosen the second possibility. The idea here is that anyway, there is a `Sprite` object that hide the complexity and the using several attributes to represent one sprite brings several advantages:

* The attributes will contain base type values, therefore it will be easy to to write them on disk. It would have been tricky to store objects.
* When one attribute changes in a sprite, the whole sprite would have to be transmitted through the I/O system. Here only the changed attribute is sent. 
	
Here is the syntax used for attributes that describe a sprite. The attributes are always put in the graph since sprites can be attached to nodes, edges or not be attached. And all this can change dynamically.
	
To specify that a sprite with identifier "S1" exists as well as its position, add the following attribute to the graph:
			
	Attribute			Value(s)
	"ui.sprite.S1"		"0.5", "0", "0"
			
To specify that a sprite "S1" is attached to node "N1", add the following attribute to the "N1" node :

	Attribute
	"ui.sprite.S1"
			
To detach it simply remove the attribute from node "N1". Do the same to attach to edges. This interface allows to attach the sprite to several edges or nodes which is dangerous, however this should be managed by the `SpriteManager` that will ensure the sprite is never attached to more than one element (later it would maybe be useful to allow attaching the same sprite to several nodes for example, and to update it only once but to paint it on each node).
			
To add an attribute "foo" with value "bar" to a sprite "S1", add the following attribute to the graph:
		
	Attribute			Value(s)
	"ui.sprite.S1.foo"	"bar"
			
To remove the attribute "foo" of sprite "S1" simply remove the following attribute from the graph:
		
	Attribute
	"ui.sprite.S1.foo"
		
To remove the sprite "S1", simply remove the following attribute from the graph:
		
	Attribute
	"ui.sprite.S1"
			
You should also remove the various attributes starting with "ui.sprite.S1." but why not keeping them if the sprite reappears ? This seems dangerous however since such attributes could proliferate.
	
How the style group set and style groups work and how to draw the graphic graph
-------------------------------------------------------------------------------

The style group set and style group make use of the style sheet to group elements by style. In fact as said above, the graphic graph does not store the graph as usual. Instead the graph is entirely stored in the `StyleGroupSet`. The style group set as is name suggests is a set of `StyleGroups` (whoa!).

Style groups are set of elements that have the same style. It is in the nature of the style sheet to define such groups. The selectors ("node", "edge", "node.foo", "node#A", ect.) often define rules that apply to several elements (the "node" selector for example applies to ALL nodes). Furthermore the groups defined by the styles are mutually exclusive, no element can be in two groups at a time. Only the "node#A" selectors (and all # selectors) apply to one element only, but they are usually few.

Therefore we can use these groups to store all the graph, and this is what is done in the graphic graph.

The purpose of doing all these groups is to ease the way things are drawn. Most of the time, graphic engines take a lot of time "pushing" graphic state before really drawing. This means that operations like defining what is the current colour, line width, texture, font, gradient, are often costly and changing them for each element is a waste of time if several elements share the same settings. For systems using shaders, it is beneficial to setup the shader only once and draw all elements that use this shader than to switch shaders constantly (which is always slow).

Furthermore, we can sort all groups following the very important Z-order. Indeed each style define the virtual plane (parallel to the screen) where the elements having this style must be drawn. Elements with a low Z must be drawn under others and with a high Z must be drawn above others. The best way to do this is to draw low Z elements first and high Z elements last. This is the Z order.

We call the fact of drawing all elements of the same style at once (thus pushing the graphic state only once before drawing the elements) a "bulk operation" or "bulk drawing".

However not all elements in a group can be drawn in a bulk operation. Some style define "dynamic style properties". For example the "fill-mode" can have the value "dyn-plain". This means that the color of the element is fixed by the style only if the element does not have the "ui.color" attribute. Else this attribute defines the colour of the element. This allows for really fast changing of the element colour (in fact the ui.color defines the index of the color to use in the style, since the style can define a palette of colours for an element).

Such element with a dynamic style attribute cannot be drawn in a bulk operation since one of the graphic state is not the same as other elements (they can be drawn in semi-bulk operation : most attributes are fixed once and some attributes (colour, width) change for each element). Another kind of element that cannot be drawn in a bulk operation are the elements actually modified by an event. There are actually two kinds of events in the style sheet : "clicked" and "selected". These events are produced by the viewer. The style sheet can define a specific style that will modify the element when the event occurs. Such "event" style are not style groups since they occur often and would imply to move the element from one style group to another often. Furthermore they inherit the style of the element they apply to and would force to create a large number of style group.

So when such an event occur on an element it cannot be draw in a bulk operation, like with dynamic values. Therefore we have three sets of elements in a style group:
	
1. Dynamic elements :  Elements that have a dynamic style attribute value.
2. Event elements   :  Elements that are actually modified by an event.
3. Bulk elements    :  Other elements.

Most of the time all the elements of a group will be in the bulk set. But it can happen that the dynamic and event subset contain some elements (in which case the elements will not be in bulk, these subsets are exclusive).

Therefore when drawing the elements of the group you first have to draw all the bulk elements. Then if they are not empty, draw the event and dynamic element subsets. The style group provide iterators for these three subsets. This way you can optimise drawing. Naturally the style group class also provides an iterator on all the elements without distinction and, if you push the graphic state for each element you can draw them using such an iterator.

How JComponent style works
--------------------------

It is possible to specify that the shape of an element be defined by a JComponent. This is done via the style property "shape" with value "jcomponent" and then the style property "jcomponent" with values "button", "text-field" and "panel".

The button and text-field allow predefined components that will modify specific attributes of the element they are attached to:

* button
	- ui.label		The label of the button.
	- ui.clicked		If the button clicked.
	- ui.last-clicked	The last time the button was clicked.
	- ui.active		Is the button usable.
	- ui.toggle		If true, the button remains clicked or unclicked.
	- style properties
		- stroke-color	colour of the button border
		- stroke-width	width of the button border
		- fill-color		colour of the button.
		- text-color		colour of the button label.
		- text-style		Style of the font.
		- text-font		Text font.
		- text-size		Size in points of the font.

Note that most of this functionality can be emulated with a standard node or sprite, the purpose of the button JComponent is to have something that "looks like" a system button.

* text-field
	- ui.label		The label of the field.
	- ui.last-changed	The last time the field was changed.
	- style properties
		- stroke-color	colour of the text-field border
		- stroke-width	width of the text-field border
		- fill-color		colour of the text-field.
		- text-color		colour of the text-field label.
		- text-style		Style of the font.
		- text-font		Text font.
		- text-size		Size in points of the font.
		
Note that this allows only to provide an editable text.

* panel	This is a standard JPanel.

For these three shapes a specific attribute "ui.handle" will contain the reference of the component (as an Object). Naturally it is usable only in the Swing thread.




© 2015 - 2024 Weber Informatics LLC | Privacy Policy