I. Introduction▲
Dans l'article précédent, Introduction à ZestIntroduction à Zest, nous
avons vu comment construire un graphe, que ce soit à partir d'un
modèle en utilisant les mêmes mécanismes que JFace, ou
directement en créant nœuds et branches. Cet article reprend
l'exemple de l'article précédent, et se propose de montrer
comment enrichir le graphe de diverses manières : soit par le
biais de styles personnalisés, de menus contextuels, ou encore par
la mise en place d'un zoom, ou de layouts différents.
Ce tutoriel suppose que vous disposiez des connaissances de
base sur les technologies suivantes :
- développement de plugins avec EclipseTutoriels sur Eclipse RCP ;
- utilisation de JFaceTutoriels sur JFace.
Par ailleurs, nous repartons sur le graphe orienté "branches" de l'article d'introduction, contenu dans la vue "LinkView". Les sources sont disponibles ici (FTP)src-intro-ftp ou ici (HTTP)src-intro-http.
II. Changer l'algorithme d'agencement▲
La boîte à outils Zest possède plusieurs algorithmes d'agencement pour un graphe, qui modifient la disposition initiale du graphe lors de sa construction. Ces algorithmes sont :
- GridLayoutAlgorithm : dispose les éléments sur une grille, comme le ferait un GridLayout Swing classique ;
- HorizontalLayoutAlgorithm : dispose les éléments horizontalement, sur une seule ligne ;
- HorizontalShift : décale les éléments qui sont hors de la zone d'affichage du graphe vers la droite ;
- TreeLayoutAlgorithm : dispose les éléments selon un arbre vertical ;
- HorizontalTreeLayoutAlgorithm : effectue la même chose que le Tree LayoutAlgorithm, mais horizontalement ;
- RadialLayoutAlgorithm : place le nœud "racine" au centre et dispose les autres nœuds tout autour ;
- SpringLayoutAlgorithm : combine les éléments de manière à ce que les connexions aient approximativement la même taille et que le minimum de nœuds "dépassent" du graphe ;
- VerticalLayoutAlgorithm : dispose les éléments sur une droite verticale ;
- CompositeLayoutAlgorithm : combine différents algorithmes choisis à la construction pour le rendu du graphe. Par exemple, on peut utiliser le SpringLayout, puis HorizontalShift pour décaler les éléments qui seraient encore hors du graphe.
La plupart de ces layouts peuvent s'utiliser sans
paramètres dans le constructeur, néanmoins, on peut utiliser
l'option "LayoutStyles.NO_LAYOUT_NODE_RESIZING" afin que le layout ne
redimensionne pas les nœuds du graphe. De même, l'option
"LayoutStyles.ENFORCE_BOUNDS" force à contenir le graphe dans la
zone d'affichage, sans dépassement.
Nous allons mettre en œuvre ces différents
algorithmes : pour cela complétez votre vue "LinkGraph"
en ajoutant un menu localisé, qui permettra de choisir
le layout à utiliser. Complétez le contenu de la
méthode "createPartControl()" en y ajoutant le code
suivant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
@Override
public void createPartControl(Composite parent) {
//(...)
//Creation de la liste des layouts disponibles
layouts = new HashMap<String, LayoutAlgorithm>();
layouts.put("Grid Layout", new
GridLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Horizontal Shift", new
HorizontalShift(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Horizontal Layout", new
HorizontalLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Horizontal Tree Layout", new
HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Radial Layout", new
RadialLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Spring Layout", new
SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Tree Layout", new
TreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Vertical Layout", new
SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
layouts.put("Composite Layout", new CompositeLayoutAlgorithm( new
LayoutAlgorithm[] {new SpringLayoutAlgorithm(),
new HorizontalShift(LayoutStyles.NO_LAYOUT_NODE_RESIZING)}));
//Creation du menu de selection des layouts
IMenuManager menuManager = this.getViewSite().getActionBars()
.getMenuManager();
MenuManager layoutsMenu = new MenuManager("Layouts");
menuManager.add(layoutsMenu); // le menu de selection apparait dans le menu
localise
Action a;
for (final String s : layouts.keySet()) {
//pour chaque layout on cree un element de menu permettant de le
selectionner
a = new Action(s, IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
viewer.setLayoutAlgorithm(layouts.get(s), true);
}
};
layoutsMenu.add(a); //l'item est ajoute au menu des layouts
//par defaut, on selectionne le Spring Layout
if (s.equals("Spring Layout")) {
a.run();
a.setChecked(true);
}
}
// (...)
}
La vue dispose maintenant d'un menu localisé qui permet de sélectionner le layout à utiliser. Bien évidemment, suivant le type de données représentées par le graphe, il sera plus judicieux d'utiliser tel ou tel layout.

III. Mettre en place un zoom▲
Zest donne la possibilité de mettre en place très facilement un système de zoom sur le graphe. Pour cela, il faut que la vue (ou l'éditeur) implémente l'interface IZoomableWorkbenchPart. D'autre part, il faut aussi définir un menu, qui permettra à l'utilisateur de choisir son niveau de zoom. Reprenez le code de la vue "LinkGraph" et implémentez l'interface IZoomableWorkbenchPart. La méthode "getZoomableViewer()" doit retourner l'objet GraphViewer. D'autre part, ajoutez un menu dans le menu localisé de la vue, comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
public class LinkGraphView extends ViewPart implements IZoomableWorkbenchPart {
// (...)
@Override
public void createPartControl(Composite parent) {
// (...)
//Creation d'un menu pour zoomer sur le graphe
//ce menu sera affiche dans le menu localise de la vue
IMenuManager menuManager = this.getViewSite().getActionBars()
.getMenuManager();
MenuManager zoomMenu = new MenuManager("Zoom");
ZoomContributionViewItem zoomItem = new ZoomContributionViewItem(this);
zoomMenu.add(zoomItem);
menuManager.add(zoomMenu);
// (...)
}
@Override
public AbstractZoomableViewer getZoomableViewer() {
return viewer;
}
}

IV. Interagir avec l'utilisateur▲
Au-delà de la visualisation, il peut être intéressant de proposer à l'utilisateur une interaction avec le graphe, par exemple pour modifier le modèle. Dans notre cas, nous aimerions que l'utilisateur puisse ouvrir et fermer des routes. L'utilisateur doit pouvoir choisir le statut qu'il veut affecter à la route à partir d'un menu contextuel accessible depuis les branches du graphe. La première étape consiste à modifier le modèle pour rajouter dans la classe Route un attribut booléen "ouverte" et les getter/setter associés. Il faut ensuite créer le menu contextuel, au sein de notre classe LinkGraphView.java. Rajoutons le code suivant dans la méthode "createPartControl" :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
@Override
public void createPartControl(Composite parent) {
// (...)
//Creation du menu contextuel
final MenuManager mgr = new MenuManager();
viewer.getControl().setMenu(mgr.createContextMenu(viewer.getControl()));
mgr.setRemoveAllWhenShown(true); //le menu est recree a chaque affichage
mgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
//le contenu du menu depend de la selection courante
IStructuredSelection selection = (IStructuredSelection)
viewer.getSelection();
if (!selection.isEmpty() && selection.getFirstElement() != null) {
Object first = selection.getFirstElement();
if (first instanceof Route) {
manager.add(new OpenRoadAction((Route)first));
}
}
}
});
// (...)
}
Nous créons dans le menu une instance de "OpenRoadAction", qui permet de définir le comportement à adopter lorsque l'utilisateur la sélectionne. Cette classe est définie comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
/**
* Cette action permet a l'utilisateur d'ouvrir/fermer les routes
* directement depuis le graphe.
* @author A. Bernard
*/
public class OpenRoadAction extends Action {
/**
* la route geree par l'action
*/
private Route route;
/**
* Constructeur.
* Definit le texte de l'action affiche.
* @param r la route selectionnee
*/
public OpenRoadAction(Route r) {
super();
this.route = r;
}
@Override
public String getText() {
if (route.isOuverte()) {
return "Fermer la route";
} else {
return "Ouvrir la route";
}
}
@Override
public void run() {
if (route.isOuverte()) {
route.setOuverte(false);
} else {
route.setOuverte(true);
}
//ne pas oublier de rafraichir le graphe
viewer.refresh();
}
}
On peut alors observer le résultat lors d'un clic droit
sur le graphe :
Notre graphe est maintenant interactif, mais rien ne permet de
voir directement si une route est ouverte ou fermée. Nous allons
donc dans la partie suivante étudier des interfaces fournies par
Zest qui vont nous permettre d'afficher ces informations, en mettant en
forme notre graphe.
V. Décorer son graphe▲
Afin de mettre en forme le graphe, il faut utiliser différentes interfaces, selon l'élément à modifier :
- l'interface "IConnectionStyleProvider" permet de modifier l'apparence des connexions ;
- l'interface "IEntityStyleProvider" permet de modifier l'apparence des entités ;
- l'interface "IEntityConnectionStyleProvider" permet de modifier l'apparence des connexions dans un graphe construit à partir de ses nœuds. En effet, rappelons-nous que dans ce cas, nous n'avons pas accès directement aux connexions.
Nous détaillons l'utilisation de ces interfaces dans les paragraphes suivants.
V-A. Mettre en forme les connexions▲
Dans un premier temps, nous allons retravailler sur la vue LinkGraphView : on se propose d'afficher en rouge les routes fermées et en vert les routes ouvertes. Reprenez le code de la classe LinkLabelProvider.java, et implémentez l'interface IConnectionStyleProvider. Complétez les nouvelles méthodes comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
public class LinkLabelProvider extends LabelProvider implements
IConnectionStyleProvider {
// (...)
/* **************************************************
* Methodes de l'interface IConnectionStyleProvider
* ************************************************** */
@Override
public int getConnectionStyle(Object rel) {
return ZestStyles.CONNECTIONS_SOLID; //valeur par defaut
//Utiliser ZestStyles.CONNECTION_DIRECTED pour afficher une fleche
// source -> destination
}
@Override
public Color getColor(Object rel) {
//Donne la couleur de base de la connexion
if (rel instanceof Route) {
Route r = (Route) rel;
if (r.isOuverte()) {
return Display.getDefault().getSystemColor(SWT.COLOR_GREEN);
} else {
return Display.getDefault().getSystemColor(SWT.COLOR_RED);
}
} else {
return null; //valeur par defaut
}
}
@Override
public Color getHighlightColor(Object rel) {
//Donne la couleur de la connexion lorsque cette derniere est
selectionnee
if (rel instanceof Route) {
Route r = (Route) rel;
if (r.isOuverte()) {
return Display.getDefault().getSystemColor(SWT.COLOR_GREEN);
} else {
return Display.getDefault().getSystemColor(SWT.COLOR_RED);
}
} else {
return null; //valeur par defaut
}
}
@Override
public int getLineWidth(Object rel) {
//Donne l'epaisseur d'une connexion
return -1; //valeur par defaut
}
@Override
public IFigure getTooltip(Object entity) {
//Affiche un tooltip sur la connexion
if (entity instanceof Route) {
if (((Route)entity).isOuverte()) {
return new Label("Route ouverte");
} else {
return new Label("Route fermee");
}
} else {
return null;
}
}
}
Chaque méthode donne directement accès à la connexion en cours de construction, ce qui permet très facilement de définir le style des éléments. Nous pouvons observer le résultat sur notre graphe :
Il est impossible d'utiliser cette méthode lorsque le graphe est construit à partir des nœuds. En effet, nous n'avons pas accès aux connexions lors de la construction du graphe. Pour cela, Zest propose l'interface IEntityConnectionStyleProvider. Reprenons la classe EntityLabelProvider :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
public class EntityLabelProvider extends LabelProvider implements
IEntityConnectionStyleProvider {
// (...)
/* ********************************************************
* Methodes de l'interface IEntityConnectionStyleProvider
* ******************************************************** */
@Override
public int getConnectionStyle(Object src, Object dest) {
return ZestStyles.CONNECTIONS_SOLID; //valeur par defaut
//Utiliser ZestStyles.CONNECTION_DIRECTED pour afficher une fleche
// source -> destination
}
@Override
public Color getColor(Object src, Object dest) {
//Donne la couleur de la connexion
return Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
}
@Override
public Color getHighlightColor(Object src, Object dest) {
//Donne la couleur de la connexion si selectionnee
return Display.getDefault().getSystemColor(SWT.COLOR_MAGENTA);
}
@Override
public int getLineWidth(Object src, Object dest) {
//Donne l'epaisseur de la connexion
return -1;
}
@Override
public IFigure getTooltip(Object entity) {
// Donne un tooltip pour le noeud selectionne
return null;
}
}
Chaque méthode permet d'accéder à la connexion définie par sa source et sa destination.

V-B. Mettre en forme les nœuds▲
Nous souhaitons maintenant afficher en rouge les villes qui ne sont plus desservies par aucune route. Reprenez la classe LinkLabelProvider.java et implémentez l'interface IEntityStyleProvider :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
>
public class LinkLabelProvider extends LabelProvider implements
IConnectionStyleProvider, IEntityStyleProvider {
// (...)
/* **********************************************
* Methodes de l'interface IEntityStyleProvider
* ********************************************** */
@Override
public Color getNodeHighlightColor(Object entity) {
//Donne la couleur du noeud si selectionne
return null;
}
@Override
public Color getBorderColor(Object entity) {
//Donne la couleur de la bordure des noeud
return null; //valeur par defaut
}
@Override
public Color getBorderHighlightColor(Object entity) {
//Donne la couleur de la bordure du noeud selectionne
return null; //Valeur par defaut
}
@Override
public int getBorderWidth(Object entity) {
// Donne l'epaisseur de la bordure du noeud
return -1;
}
@Override
public Color getBackgroundColour(Object entity) {
//Donne la couleur de fond par defaut des noeuds
if (entity instanceof Ville) {
Ville v = (Ville) entity;
//On regarde si les routes qui desservent la ville sont toutes
// fermees ou non
boolean estReliee = false;
for (Route r : Model.INSTANCE.getRoutes()) {
if (r.getSource().equals(v) || r.getDestination().equals(v)) {
// La route consideree dessert la ville.
// Si elle est ouverte, la ville est accessible par au
// moins une route
if (r.isOuverte()) {
estReliee = true;
}
}
}
//Si la ville est reliee par au moins une route, la couleur
//affichee est celle par defaut, sinon le noeud est colore en rouge
if (estReliee) {
return null;
} else {
return Display.getDefault().getSystemColor(SWT.COLOR_RED);
}
} else {
return null;
}
}
@Override
public Color getForegroundColour(Object entity) {
//Donne la couleur d'avant-plan des noeuds (le texte par ex.)
return null; //valeur par defaut
}
@Override
public boolean fisheyeNode(Object entity) {
//Cree un zoom sur l'entite quand elle est selectionnee
return true;
}
}
Une nouvelle fois, chaque méthode nous permet d'accéder très facilement à l'entité en cours d'affichage. Remarquez la méthode "fisheyeNode" qui permet d'effectuer un zoom sur le nœud lorsqu'il est survolé par la souris. Observons le résultat :
VI. Filtrer certains éléments▲
Zest nous permet aussi de mettre en place des filtres sur les graphes, de la même manière que sur les viewers JFace. Créez le filtre suivant, qui nous permettra de cacher les villes qui ne sont desservies par aucune route :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
package com.abernard.zest.viewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import com.abernard.zest.model.Model;
import com.abernard.zest.model.Route;
import com.abernard.zest.model.Ville;
/**
* Ce filtre permet de cacher les villes desservies par aucune route. Si
* la ville n'est pas reliee par des routes, elle n'est pas affichee.
* @author A. Bernard
*/
public class EntityFilter extends ViewerFilter {
@Override
public boolean select(Viewer viewer, Object parentElement, Object element)
{
// Determine si la ville doit etre affichee, ou non
if (element instanceof Ville) {
Ville v = (Ville) element;
//On regarde si les routes qui desservent la ville sont toutes
// fermees ou non
boolean estReliee = false;
for (Route r : Model.INSTANCE.getRoutes()) {
if (r.getSource().equals(v) || r.getDestination().equals(v)) {
// La route consideree dessert la ville.
// Si elle est ouverte, la ville est accessible par au
// moins une route
if (r.isOuverte()) {
estReliee = true;
}
}
}
//Si la ville est reliee par au moins une route, la ville est
// affichee, sinon elle est filtree
return estReliee;
} else {
return true;
}
}
}
Ajoutez ce filtre au graphe dans la vue LinkGraphView.java :
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Override
public void createPartControl(Composite parent) {
// (...)
//Definition du filtre
viewer.addFilter(new EntityFilter());
// (...)
}
Observez maintenant le résultat lorsque l'on ferme la route qui relie Metz à Paris :

VII. Conclusion▲
Nous avons vu dans cet article comment modifier l'affichage du
graphe et améliorer son ergonomie. Au terme de ces deux articles,
nous pouvons constater que Zest est un outil de visualisation de
graphes puissant tout en étant pratique et facile à utiliser,
et qui propose assez d'options pour représenter des graphes clairs
et précis, adaptés au modèle de chaque application.
Cette boîte à outils est notamment utilisée
pour l'outil de visualisation de dépendances de PDE
(Plugin Developppement Environment), actuellement dans
l'Incubator d'Eclipse : PDE Incubator
Dependency VisualizationPDE Incubator Dependency Visualization.
VIII. Liens utiles▲
Pour aller plus loin, voici quelques liens utiles :
IX. Remerciements▲
Je tiens à remercier pour cet article les membres de la communauté Java pour leurs remarques et leurs conseils. Un grand merci aussi à jacques_jean pour sa relecture attentive.









