A Common Design for Vector Graphics GUI Component

Author: Mikhail Vladimirov <vladimirow@mail.ru>
Thanks to: Maxim Belushkin, Igor Konnov, Kirill Krushniakov

1. Introduction

There are many GUI components available which can be used to display vector graphics and animation. Most of them are dedicated to particular classes of vector images such as graphs, business diagrams, SVG images, geographic maps, technical drawings and financial charts. Unfortunately, it is not yet always possible to find a suitable component for a particular application, which satisfies price, licensing, scalability, performance, stability, feature availability and other requirements. And if one wants to add vector-based visualization to an existing application, additional requirements of integration ability arise.

So from time to time developers have to implement their own components. A good way to create a new component is to get an existing one and adapt it. If the original component is clearly designed and not too complex, the modifications should be quite simple. But in the real world this way is not always acceptable. The license of the component may prohibit modifications, source code might not be available or its quality may not be good enough. Even if the component is clearly designed and its license permits to modify the source code, it is not always possible to satisfy all the requirements using only small changes. Scalability, performance, stability or integration ability can not be easily improved in most cases. In the worst case the developer has no option but to create a totally new component from scratch.

The goal of this article is to present a common design for GUI components that are intended to display vector-bases images. A public domain implementation of this design written in the Java language can be obtained from http://jdrawing.sourceforge.net/. This implementation can be used as a starting point for development of custom components.

2. Overview

In this article term vector-based image means an image that is a result of a known sequence of simple painting operations. The set of allowed simple operations depends on concrete application.

When image areas affected by several painting operations overlap each other, the sequential order of execution of such operations can be important. However in some cases the same set of operations being executed in different sequential orders can produce the same image. This makes it possible to use a weakly ordered set of painting operations to represent vector-based image rather than the exact sequence.

The easiest way to display a vector-based image is to perform all corresponding painting operations one by one in a suitable order. While this approach is widely used, it is not effective. Let's consider a large vector-based image displayed inside a small scrollable window. When a user scrolls the window, small pieces of the image should be updated frequently. Performing all painting operations at every update, while most of them do not affect the wasted region at all, can produce significant overhead. The design presented in this article makes it possible to use different caching techniques to speed up screen updates. These techniques can be implemented independently from the data model of the image and can be replaced at runtime.

An important question is how to represent painting operations in terms of a programming language. Many of the object-oriented graphics frameworks use the following approach. They define special interface that contains all methods necessary to perform painting operation, such as "draw", "getBounds" and so on. Each painting operation is represented by an object that implements this interface. This kind of representation of painting operation is called self-renderable because the representation itself can perform corresponding operation. The main disadvantage of this approach is that all information necessary to perform an operation should be available via its representation. While this sounds as an obvious solution, it can be inconvenient in some cases. Let's consider a GIS application, which displays geographic maps. A map data model consists of objects that represent geographical entities such as roads, cities and so on. Each object stores the location of the corresponding entity using geographical coordinate system based on latitude and longitude values. To paint a map on a plain surface such as computer screen some kind of projection from geographical coordinates into plain rectangular coordinates should be used. So the information about attributes of the projection is necessary to paint geographical entity. If this information will be available via representing object of the entity, then it will be hard to display the same map in different projections simultaneously, because the same objects should use different projection settings while being rendered in different views.

The following approach can help in the case described above. The projection attributes are stored inside an additional object that is not a part of the data model of the map. This object implements special interface that contains methods like "draw" or "getBounds". This methods accepts an additional argument that specify a painting operation in subject. For each view of the map separate object with its own projection settings can be used. In this article such additional object is called element renderer. The design of the component presented in this article is based on the second approach, but the first approach still can be emulated via a special element renderer.

3. Design basics

All functions of the component are distributed between the following objects: control, data model, data model cache and element renderer. The control is responsible for the flow of execution of all other elements and the communication with the GUI framework. The data model maintains information about vector-based image. The same data model object can be shared between several control elements. The data model cache incapsulates optimization algorithms. The element renderer is responsible for the execution of painting operations in the process of image construction. All listed objects communicate via simple interfaces, and can be independently replaced. Below, you shall find a description of all interfaces and the structure of the named objects.

3.1 Control

The interfaces implemented by this object depend on the underlying GUI framework. For example, if underlying GUI framework is Swing for Java then control should inherit class JComponent. When some part of the image should be updated control refers to the data model cache to determine which painting operations affect wasted region, as well as to element renderer to perform these operations.

3.2 Data model

The data model is a wrapper for storage of image data. It allows one to obtain all painting operations of the image and to sort a given set of operations in such a way that all overlapping operations are properly arranged. The Java implementation requires the following methods to be implemented by the data model object:

Enumeration elements ()
void sortElements (Object [] elements)

Upon changes applied to the set of operations or their execution order, data model sends appropriate signals to the data model cache.

3.3 Data model cache

Data model cache is an object than incapsulates optimization algorithms. It allows one to perform the following operations effectively:

The Java implementations requires the following methods to be implemented by the data model cache:

Object[] getElementsForPoint (Point2D point)
Object[] getElementsForRectangle (Rectangle2D rectangle)
Rectangle getModelBounds ()
Rectangle2D getModelBounds2D ()

In the process of execution of these methods all the required information is retrieved from the data model and the element renderer objects. To increase performance, some information may be cached inside the data model cache.

Upon receiving a notification about changes inside the data model, the data model cache calculates the boundary of the region to be redrawn, updates cached information and sends an appropriate signal to the control.

3.4 Element renderer

This object is responsible for execution of operations required for the construction of the image. Besides that, it is capable of calculating the boundary boxes of given operations and checking, whether an operation affects a given point or rectangle. This functionality is implemented via the following methods:

boolean elementContains (Object element, Point2D point)
boolean elementIntersects (Object element, Rectangle2D
rectangle)
Rectangle getElementBounds (Object element)
Rectangle2D getElementBounds2D (Object element)
void paintDrawingElement (Graphics graphics, Object element)

4. How it works

In this section we shall demonstrate the use of the component in a Java program. First and foremost, the structure of the data model has to be determined. The elements of the model have to be determined, as well as a means of their storage. The selection of a set of elements is a key step in the development of data models. An ideal element set selection will provide elements that

In cases when the two criteria can not be simultaneously satisfied it may be possible to apply to notion of compound elements (see below).

4.1 Model structure

The storage of elements inside a data model can have different implementation approaches. The existing implementation contains the following standard models: DefaultDrawingModel, responsible for storing the elements in an unsorted fashion, and LayeredDrawingModel, which allows to assign a layer index to each element.

4.2 The element painting process

Once the model structure is given, one has to decide how the elements shall be displayed. The following possibilities exist:

4.2.1 Self-renderable elements

The functionality required for the output of an element is encapsulated inside the element. In Java, the implementation requires that an interface DrawingElement is implemented by the object. This interface includes the following methods:

boolean contains (Point2D point)
Rectangle getBounds ()
Rectangle2D getBounds2D ()
boolean intersects (Rectangle2D rectangle)
void paint (Graphics graphics)

This approach is applicable in cases when the output procedure of an element is uniquely determined by its type and attributes; this usually holds for elements, which represent graphical constructs, i.e. geometrical figures.

4.2.2 Element renderer

The element renderer incorporates the functionality required for the painting of all elements of arbitrary types. This approach is most useful when the data stored inside an element does not contain enough to paint the element, or when off-memory model is used.

4.2.3 Compound elements

The set of operations required to paint an element is distributed between several smaller objects. In Java, one should implement the interface CompoundDrawingElement inside the object; this interface includes one method:

Enumeration elements ()

This method allowed to obtain a list of all elements, which form the compound element. This approach should be used when the graphical representation of the object is complex. The painting operations related to each element in the compound object may be implemented in any of the described ways.

4.3 Optimization

Once the data model is built, and the painting operations are implemented, one should think about optimization. The Java implementation offers two standard optimization algorithms inside the classes GridDrawingModelCache and QuadTreeDrawingModelCache. When used, these algorithms can significantly increase the execution speed of the component at the cost of more memory consumption. The implementation architecture allows to replace the optimization algorithm on the fly, as well as to use new algorithms.

4.4 Off-memory and indexed models

In GIS and some other applications very big images consisting of millions of elements can be used. Storing the complete description of such image in virtual memory is not reasonable. Relational database of some other external storage can be used instead. In this case operations will be represented by the identifiers of the corresponding records in the storage. The element renderer retrieves the actual data record when it has to perform operation.

This approach can make standard optimization algorithms ineffective because they can not utilize advanced features of the external storage such as database indexes. Java implementation supports concept of indexed data models that helps to deal with this problem. Indexed data model is a data model, which implements two additional methods:

Enumeration elements (Point2D point)
Enumeration elements (Rectangle2D rectangle)

These methods are used to enumerate operations that affect given point or rectangle respectively. These methods can use all features of the external storage.

5. Similar projects

5.1 JGraph

http://www.jgraph.com/

JGraph is the most powerful, lightweight, feature-rich, and thoroughly documented open-source graph component available for Java. It is accompanied by JGraphpad, the first free diagram editor for Java that offers XML, Drag and Drop and much more!

With the JGraph zoomable component, you can display objects and relations (networks) in any Swing UI. JGraph can also be used on the server-side, for example to read a GXL graph, apply a custom layout algorithm, and return the result as a HTML image map.

5.2 OpenJGraph

http://openjgraph.sourceforge.net/

Opensource Java library to create and manipulate graphs and graph drawings

Current features include:

5.3 Magelan

http://magelan.hit.bg/

This project provides a library of easy to use 2D graphics editor tools and a functional and extendable stand-alone graphics editor.

5.4 JChart

http://www.ilpiola.it/roberto/jchart/index_e.html

JChart is a good piece of code by Roberto Piola; it was born as an applet for displaying some data on a web page, in a manner similar to what gnuplot does on any machine (and M$ Excel does on a PC): histograms, plots, and so on.

In the following, it was extended, reducing the core of JChart to a reusable component, and obtaining an applet and a stand-alone application as by-products. The data can be passed as a file name, as a data structure or as an URL, and they can be visualized in several alternative ways.

5.5 Batik

http://xml.apache.org/batik/

Batik is a Java(tm) technology based toolkit for applications or applets that want to use images in the Scalable Vector Graphics (SVG) format for various purposes, such as viewing, generation or manipulation.

The project's ambition is to give developers a set of core modules, which can be used together or individually to support specific SVG solutions. Examples of modules are the SVG Parser, the SVG Generator and the SVG DOM. Another ambition for the Batik project is to make it highly extensible (for example, Batik allows the developer to handle custom SVG tags). Even though the goal of the project is to provide a set of core modules, one of the deliverables is a full fledged SVG browser implementation which validates the various modules and their inter-operability.