Core evolution - Preview Refactoring

From Gephi:Wiki

Jump to: navigation, search

Contents

Introduction

The Preview module is a major brick in Gephi, it brings display customization, with lots of options and vectorial formats (SVG, PDF) exports. It has been originaly developed for GSoC 2009 and is now in a version 1.0 with a very complex architecture and no possibility to extend it's feature with plug-ins. The need for plug-ins to add or change settings, shapes and everything else is essential.

Why Preview?

For guaranteeing best performance, the 3-D visualization engine leaves apart effects and complex shapes, for instance curved edges and focus on basics, i.e. nodes, edges and labels. As it is specialized in a particular task, the visualization engine is thus less flexible. That's why a complementary drawing library, is needed to bring parametrization and flexibility.

Preview doesn't need performance, it needs flexibility.

Flexibility

What is the flexibility we are looking for?

Kind of things that should be possible to do with Preview Plug-ins:

  • Add a new type of shape, for instance Convex hulls
  • Add new properties (settings)
  • Override how anything is rendered by providing a new implementation
  • Add new exporters
  • Provide complex UI for configuring properties
  • Execute complex algorithms before rendering

The Force-Directed Edge Bundling is a good example. It is supposed to modify how edges are rendered using complex calculations on the distances. When enabled it therefore overrides default edge drawing.

Current design

The preview related plugins are the following:

  • Preview API - as mentioned in the overview file, the API for building the graph preview structure
  • Preview Impl - a set of classes implementing the Preview API interfaces
  • Desktop Preview - contains the UI classes of the preview, especially the Processing applet redering preview graphs
  • Preview Export - contains preview export classes, such as the SVG and the PDF ones

Abstract

In fact, the preview graph is rendered in the GUI using data from a preview graph structure, which is built from the current workspace graph data. The PreviewController controls this preview graph structure building, which is processed by the PreviewGraphFactory.

The preview settings are stored in objects called supervisors, which are hold in the PreviewModel. In addition, supervisors are implementing a kind of caching system. Indeed, when a preview graph element is created in the PreviewGraphFactory, it is set as a kind of listener to its related supervisor. Then, when a preview setting is changed, the supervisor storing it notifies all the preview graph elements, telling them to update the concerned fields. For instance, when the node color setting is changed in the preview, the new color is set in the NodeSupervisor, and preview nodes listening to it are asked to actually change their own color. Therefore, elements' fields of the preview graph structure are not dynamically computed at each call, but only at the element creation, or when a preview setting is changed.

Issues

Besides its lack of modularity, the design is clearly over-architectured, abusing OO concepts like inheritance.

  • Too much classes, why duplicating java.awt.Color?
  • It duplicates the graph structure in its own static model. Not bad, as having a copy at one particular moment is good idea, but its too static, it remains a graph model.
  • Everything is exposed in the Preview API. No cluelessness or use-case design.
  • There is no real concept of Property, and serialization could be improved.
  • No concept of rendering targets, SVG, PDF and Processing are not unified by an interface

UI

The UI code is located in the PreviewDesktop module and have two TopComponents, one for display and one for settings. The controls remains simple and settings are based on a PropertySheet, with a set of customized Property Editors.

New design

Thought the amount of code is quite important, and it has a lot of useful options, there is no choice to completely drop the current implementation. However, it's above all an architecture issue and routines to render these or these objects could be ported in a new architecture.

The new design should be closer from a general graphic framework, with basic concepts like:

  • Item: What it at screen
  • Renderer: How an item is renderer
  • Builder: How an item is built

It is greatly recommended to analyse other librairies and how they deal with these issues, in particular:

Item

Items would be Nodes, Edges, Labels, Mini-labels or any other thing displayed a screen, like convex-hulls. They would be the place where to store data and properties values, in a way anyone can access these data.

For instance:

public interface Item {
    public Object getSource();
    public <D> D getData(String key);
    public void setData(String key, Object value);
}

Renderer

The renderer's role is to use Item's data and display them to rendering targets (SVG, Processing, PDF, ...). One can imagine one renderer for each type of element, a node renderer, an edge enderer, a label renderer etc.

However it's important to see that renderers will be often overridden, when someone want to change how things are displayed.

Below are some examples of what plug-ins developers may want to do. These actions would require to override default renderers.

  • Put a color gradient for edges, from source color to target color
  • Make labels vertical
  • Render a square instead of a circle for nodes
  • Render a picture instead of a circle for nodes
  • Modifiy text position, aligned at the right of the node, plus a margin
  • Curve edge text along the edge (when curved edges enabled)
  • Render meta-edges using dashed lines

Sometimes the default renderer should be completely overriden (make all labels vertical) and sometimes it concerns only particular elements (only if the edge is a meta-edge for the last example). So the mechanism should be flexible enough, and the decision which renderer to use for a particular item should be well designed. Moreover there could be conflicts between renderers, with sereral renderers valid for an item, which one to choose?

Builders

Builders are the factories which will create Items. Items have a source and data. Objects in the graph (nodes, edges, labels) creates items and properties of the objects are the data.

It is important to copy all parameters into items, and not to rely on 'Node' and 'Edge' from Graph API anymore. We would have something immutable like this. However it could be a mistake to completely cut the link, we may need the reference to the original object somewhere, that's why 'Item' has a 'getSource()' method, it would be the Node, the Edge, the Label. This particular point can be discussed however.

public interface Builder {
    public Collection<? extends Item> getItems(Graph graph);
}

Builders or a close concept, let's say DataBuilder should be responsible of pushing data into Items. This should be modular as well, any plug-in should be able to extract additional information from objects and push it to the Item. For example, with the 'Render meta-edges using dashed lines' use-case, an plug-in class will push a 'isMetaEdge' value into each edge, in order it can be used later by it's renderer.

Some values can be the result of some calculations, this should be considered. For instance the mini-label renderer needs the angle of the edge. The arrow renderer needs the distance between the center of the node and the border (which is trivial when the node is a circle, less trivial when it's a rectangle). Moreover an order should be defined? What if some calculation needs the result of another calculation to perform?

Properties

Properties should have a way to define how they are accessible/enabled. A tree structure could be interesting to model that a property is valid only when it's parent is. It would also help to define boolean properties for enabling particular options. For instance, labels borders have several options (color, shape, margins, ...) that should only be enabled when 'Label Border' property is enabled.

The way how some properties depend on each other will be a problem. The idea that a property defines a parent property, boolean, may not be enough to cover all use-cases. For example, edges mini-labels are possible only on straight edges. Moreover, the possibilty to disable any property, just knowing it's name should be possible from the API.

The choice for renderers may be linked with these 'enable' properties.

Rendering targets

Current rendering targets are SVG, Processing and PDF. Renderers's role is display items to the targets.

There is currently no way to unify rendering targets. The code to display a circle or a text is different in Processing, SVG and PDF. A attempt to build the PDF output from the SVG failed, due to performance reasons. There is currently no other choice than write three times almost the same code to display something. One of the main issue is that different systems don't have the same coordinate system, centered, upper left or bottom left.

That means that each renderer has to have three method 'renderProcessing()', 'renderSVG()' and renderPDF()' ? Probably. Do we need another abstraction to let plug-in developers add new targets ? Probably not by default. Keeping Renderer simple is important. For additionnal target, one can imagine rather a new interface that only plug-in willing to add a new target would implement. if its complex it doesn't matter, it's more important to keep simple the obvious case.

UI

The Property Sheet is a good idea, as it allows to combine simple and complex settings, with the help of Property Editors.

Status

Implemented in revision 2300+ and following. This page doesn't exactly reflect the current API but close. Note that the PreviewImpl module has been deleted.

Personal tools