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

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

The style sheet mechanism
=========================

I suppose such a way of doing is applicable to many "viewers" so I will try to broaden the discussion when possible. However, most of the time we deal with the graphical representation of a graph.

The style sheet is a collection of styles that apply to graph elements (hereafter just "elements") to produce a visual representation of the elements data.

Therefore, at the style sheet level, styles describe:

- styles that apply to one precise element 
- styles that apply to a class of elements;
- styles that apply to a kind of elements;
- styles that alternatively apply to elements when a special condition occur.

The classes are arbitrary groups created by the user of the style sheet to define a style once for many elements. It allows a dynamic rebinding of styles by adding or removing an element to or from a class attribute ("ui.class").

The kinds of elements are fixed, they are :

- graph;
- node;
- edge;
- sprite.
	
In the style sheet, the selectors can therefore be:

- Kind -> apply to all elements of that kind :
	- graph {}
	- node {}
	- edge {}
	- sprite {}
- Class -> apply to all elements of a kind with a given class :
	- node.foo {}
	- edge.bar {}
- Specific -> apply to a precise element of a precise kind :
	- node#A {}
	- edge#AB {}
- Event -> apply to an element when a condition is met :
	- node:clicked {}
	- node#A:clicked {}
	- node.foo:clicked {}
		
The events are special styles, because in fact they enlarge an existing style by providing an alternative set of (property,value) pairs to a style. That is, when the style sheet is parsed, all styles are mapped to a selector to match elements of the graph, excepted event styles that match another style and extend it.
		
Dynamic values
--------------
		
Some styles have dynamic values. The dynamic values allows to change very quickly some styles
values like widths and colours, without having to parse anew a style or style sheet. For example
the colour of an element can in fact be a set of colours, defining a colour map. Then one can tell
that the colour is an interpolation between colours in this colour map. In the same way, the width
of an element is something that can greatly vary. 

Such a behaviour is controlled by specific values for the style, and additional attributes. For
example the "fill" style property controls the way the element contents is painted. This fill value
can take the value "dyn-interpolation". In this case by default only the first colour of the colour
map is used to fill the element. Then if an attribute "ui.fill" is added or changed on the element,
with a numeric value in [0..1], this value is used to intepolate the colour in the sequence of
colours of the colour map. For example if there are three colours, 0 means the first colour, 0.5
means the second colour and 1 the third colour. But 0.25 gives a mix between the first and second
colour.   
	
Architecture for the style sheet mechanism
------------------------------------------

We plan to display a styled graph as fast as possible. Most of the time, with many viewers, the
fastest way to draw things is to drawn all elements with the same style at once. This is due to
the fact most of the time changing drawing attributes (colour, stroke, texture, etc.) is costly.
This is indeed true for Swing and OpenGL which are the two major drawing platforms we envision to
create a viewer for.

Furthermore, we want to be able to draw as many elements as possible, as fast as possible. The
viewer must be able to draw the largest set of elements possible. Therefore, internally, the
style sheet is organised to help such a way of drawing elements.

First the style sheet contains a set of basic styles. A basic style is a set of pairs
(property,value). The basic styles can inherit one another. The inheritance model is fixed. This
means that:

- Kind styles all inherit a default root style having all default values set.
- Class styles inherit the kind styles.
- Specific styles inherit the kind styles.
- Event styles inherit the style for which they provide an alternative.
	
This inheritance is said "vertical".
	
As not all specific styles map to each element, we need a way to assign styles to elements. However,
the link between elements and styles may be quite dynamic (and we want it to be this way). Therefore
the style sheet also contains a set of style groups. A style group contains all the elements that
must be drawn with this style.

This allows to explore the style sheet by style group. These groups will be sorted using the Z
value. This allows to draw the elements that should appear under others first. This way of doing
is also applicable when styles have dynamic values, since all the elements with a dynamic value will
be in the same group. One can expect such groups to be rendered more slowly, since some graphic
attributes will change during rendering of the group. But this still can be optimised.

In addition to vertical inheritance, an element can also be styled using class styles. The class
mechanism is called "horizontal inheritance" or "aggregation", because an element can refer to a
kind or specific style and at the same time to zero or more class styles. The class styles must
be aggregated to the kind or specific style of the element. 

The style group role is to tie all these components and map them to elements. There is one group
for each combination of horizontal and vertical inheritance used by elements. This allows to
push all the graphical settings once in the graphic system, and then draw all the elements that
follow this style.

In other words, the style sheet will only store the various basic styles and the matching rules
(the rules tell to which elements a style apply). When given an element, the style sheet will be
able to answer by a list of styles that apply:

- One "main" style that necessarily apply to the element. This main style is a kind style or a specific style.
- Zero or more class style, with a specific order (the one given by the user in the "ui.class" attribute), that have a higher priority than the main style. When there are several class styles, their order place the first class style as being at the highest priority.
	  
	Style group =  [MainStyle] + [ClassStyle1, ClassStyle2, etc.]
	  
As the class styles always inherit the kind styles, and as the specific styles also always
inherit the kind styles. The classes necessarily inherit the same styles than the main style
(or inherit the main style directly). Therefore the inheritance of the classes is never
used.

Therefore, when we look at the value of style property in a style group, we look horizontally
to the classes (aggregative style), and then vertically to the main style and its parents.

	         ^
	         |
	     5 default
	         |
	      4 kind
	         |
	  -- 3 specific -- 1 class -- 2 class -->
	         |
       
The diagram above shows how we look for the value of a property in the style group of an element
that have a specific style. It also give the order of the search.

	         ^
	         |
	     4 default
	         |
	   -- 3 kind -- 1 class -- 2 class -->
	         |
         
The same diagram when looking at elements that have a style that apply to all elements of their
kind, and some classes.

In presence of events, we merely replace the styles by their alternative style if there is one
(here considering the specific style and all classes have alternative styles for events) :

	         ^
	         |
	     8 default
	         |
	      7 kind
	         |
	    6 specific      2 class     4 class
	         |             |           |
	   -- 5 event ----- 1 event --- 3 event -->
	         |
        

The special case of event styles
--------------------------------

Event styles are alternative styles to a given style. Therefore these styles are somewhat like
classes, they aggregate to the style, but they do it temporarily, and moreover, as said above,
they are not tied to a specific element, but to a style. Therefore they are not inserted inside
style groups, but inside styles. In fact, they participate to the definition of style. When an
event is specified, it enlarges a style by an alternative set of (property,value) pairs.

An example of the style sheet internal organisation
---------------------------------------------------

Given a the following graph:

	         ab
	    (A)------(B)           B: ui.class=foo
	     |        |            C: ui.class=foo
	   ad|        |bc          D: ui.class=foo,bar
	     |        |
	    (D)------(C)
	         dc

And the style sheet:

	default        { ; }       <- The default style is a root style with all its properties
	graph          { ; }       having a default value. All styles inherit it.
	node           { ; }
	edge           { ; }
	node#A         { ; }       The styles (property,value) pairs are not given since they
	node#B         { ; }       are not necessary for this example. We have only defined
	node.foo       { ; }       nine styles (the default style always being defined for us).
	node.bar       { ; }
	node:clicked   { ; }
	node#A:clicked { ; }
	
This creates the following rules:

	rule1: { graph,     }
	rule2: { node,      }
	rule3: { edge,      }
	rule4: { node#A,    }
	rule5: { node#B,    }
	rule6: { node.foo,  }
	rule7: { node.bar,  }
	
This creates the following style groups:

	         +---------+----------------+-------+--------------+------------+
	         | Element | Element        | Main  | Aggregates   | Unique     |
	         | kind    | matches        | style | styles       | identifier |
	+--------+---------- +--------------+-------+--------------+------------+
	| group1 | graphs  | /              | rule1 | /            | g          |
	| group2 | edges   | ab, bc, dc, ad | rule3 | /            | e          |
	| group3 | nodes   | A              | rule4 | /            | n_A        |
	| group4 | nodes   | B              | rule5 | rule6        | n_B(foo)   |
	| group5 | nodes   | C              | rule2 | rule6        | n(foo)     |
	| group6 | nodes   | D              | rule2 | rule6, rule7 | n(foo,bar) |
	+--------+---------+----------------+-------+--------------+------------+
	
Therefore for any combination of (element-kind,id,classes) we create a new group, this is shown
by the "unique-id" column. You can also see, for each group, the kind of elements in the group,
the elements that match the group, aggregate of style rules.

Determining the value of a style property for style groups
----------------------------------------------------------

Here is the inheritance hierarchy :

		Default
		  |
		  +---- Graph
		  |
		  +---- Sprite
		  |
		  +---- Edge
		  |
		  +---- Node
		          |
		          +---- Node:clicked
		          |
		          +---- Node#A
		          |       |
		          |       +---- Node#A:clicked
		          |
		          +---- Node#B
		          |
		          +---- Node.foo
		          |
		          +---- Node.bar

As styles inherit or aggregate one each other, it can seem difficult to know which value is used.
Furthermore styles can be overridden by event styles, and classes can aggregate to them. One can
know the final value of a style property for an element using the style group. Here are several
examples for the graph and style sheet used as example above.

The style sheet, as far as it is concerned only yields the list of rules that apply to an element.
Therefore, several rules can apply. The style groups, as we have seen it, contain this list of rules
as well as all the elements to which they apply.

When the value of a style property is queried, the style group therefore must explore the style
rules it aggregates. It first search in the classes rules, that have a higher priority, then at
the main rule of the style which is either a specific style or a kind style.

When exploring the main rule of the style, if the style has no value available, the parent style
in the inheritance hierarchy is looked at, until the root style.

When exploring classes, it only look at the class style, but not to its parents. This is not
necessary, since, class styles inherit the same parent as the main style (an invariant of the
fixed hierarchy), and also because we must first look at the main rule anyway.

When an event is occurring, the process is the same, but if a class style or main style has an
alternative for the event, this one is used instead. 

Therefore, here is the general algorithm to find the value of a property for a given style group:

- If an event is actually occurring, push it on the main style and any of the aggregate styles when we look at them.
- If the group has classes (aggregate styles)
	- for each class in order
		- if the class has the property use its value and stop searching following classes.
		- else look at the next class.
	- if a class as the property use this value
	- do not look at classes parents, since they inherit the same parent as the main style.
- Else if the property was not found or there are no classes
	- look at the value in the main style.
		- If the main style has a value, use it.
		- Else look at its parent.

Example for edges:

- The value for edges is easy to obtain. There is only one rule, which contains only one style.
- The value is the one in the 
- If the  defines the value for the property we use it
- Else if look at the  parent in the inheritance hierarchy, that is the default style.

Example for node A:

- The A node has only one main style . However if there is a "clicked" event, the  is overridden by .
- Depending on the event, the style 4 or 9 is searched for the given property.
- If it has it, it is used, else the parent style is searched. For  the parent style is the default node style, for  the parent style is .

Example for node B:

- The B node has one main style  and one aggregate style .
- If there is a "clicked" event, the alternative styles are searched. There is not alternative style for  and , but  and  inherits  that have an alternative style. This one can be picked in there is an event.
- Else we first look at , and then else at .
- Else we look at their parents.
	
Style group structure
---------------------
	
Now lets see how the groups can be organised. We need something that allows to insert and remove
elements in groups as fast as possible.

One possibility is to use the unique identifiers. The graph is a set of groups, each with a given
unique identifier. The groups are constructed this way, if we keep the example above :

Assigning a group identifier to an element:

- Start with an empty identifier.
- Add the element kind : g, n, e or s (for graph, node, edge or sprite)
- If a rule for the identifier of the element exists add the element identifier prefixed by a "_" character.
- If the element has classes add a "(" character, then the name of all classes in order, separated by commas "," characters, and then add a ")" character.
	
Note : the name of the classes are used in order. This means that the order in which the
user put classes in the "ui.class" attribute matters. To convince oneself of this,
consider an element which has a style and two classes that both define the
"color" property. Which colour do we use ? Only the one of the first class in the "ui.class"
attribute order. We need this order to know which class to use when a conflict occurs. 
	
Adding an element:
		
- Find the element group identifier.
- If the group does not exist :
	- Create a new group.
	- Assign it the group identifier found for the element.
	- Add it all the rules the element matches.
	- Insert the group in the group set.
	- Add the element to the group.
- Else merely add the element to the group.

Removing an element:

- Get the group of the element, knowing its group identifier.
- Remove the element from the group.
- If the group is empty, destroy it ? (maybe we can conserve the group for a while, to avoid recreating groups, but we need a way to be sure that an empty group will ultimately disappear).
	
Proposition
-----------

Instead of storing the graph as a usual graph structure, we can store it as a set of style groups,
as defined above plus the connectivity between nodes. This is what is done in the graphic graph
package.

Drawing the elements
--------------------

The idea is to explore the style groups. We do this in a specific order however. The order follows
the "z-index" property. All groups are not only stored in a map (unique-id,group), but also in what
we call a Z list.

The Z list contains all the groups in order, sorted by their "z-index" style property. Then for each
group, we push the graphic attributes corresponding to the style group in the graphic system and 
iterate on the elements of the group to draw them with these graphic attributes.

A note on the cascade
---------------------

In addition to inheritance, style can cascade in HTML/CSS. However here the cascade is not this
important, since elements of the graph are not really nested one in another. Indeed nodes and edges
are "inside" the graph, but due to the large difference between nodes/edges and graphs it would not
be wise to make nodes and edges cascade the style of the graph.

The part of the cascade that concerns addition of styles (several style sheets accumulate) is
however possible.

How all these things are tied
-----------------------------

At the heart of the package is the StyleSheet class that contains all the styles. The styles are
added by the parser. The Style inherits the StyleConstants that define the possible values for some
style properties as well as methods to process properties values. The style sheet references styles
using Rule objects that are a pair (selector,style). The Selector defines which graph elements
the style matches.

The StyleGroup defines a group of graph elements that share a style. The styles in the group are
in fact set of Style instances from the style sheet. The style group is able to process them to give
unique value to each style property. 

The StyleGroupSet handles all the style groups, is in charge of assigning elements to groups, removing
elements from groups, and handling changes in the style sheet and their effect on the groups.




© 2015 - 2024 Weber Informatics LLC | Privacy Policy