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 you 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 cannot 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-based 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.
In this article, the 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 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 a 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 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 a special interface that contains all methods necessary to perform painting operations, such as "draw", "getBounds", etc. Each painting operation is represented by an object that implements this interface. This kind of representation of painting operations is called self-renderable because the representation itself can perform the 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 like 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, etc. Each object stores the location of the corresponding entity using a geographical coordinate system based on latitude and longitude values. To paint a map on a plain surface such as a 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 a geographical entity. If this information will be available via the 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 a special interface that contains methods like "draw" and "getBounds". These methods accept an additional argument that specifies a painting operation in the subject. For each view of the map, a separate object with its own projection settings can be used. In this article, such an additional object is called an "element renderer". The design of the component presented in this article is based on the second approach, but the first approach can still be emulated via a special element renderer.
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 images. The same data model object can be shared between several control elements. The data model cache encapsulates 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.
The interfaces implemented by this object depend on the underlying GUI framework. For example, if the underlying GUI framework is Swing for Java, control should inherit the class JComponent. When some part of the image should be updated, control refers to the data model cache to determine which painting operations affect the wasted region, as well as to the element renderer to perform these operations.
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, the data model sends appropriate signals to the data model cache.
The data model cache is an object that encapsulates optimization algorithms. It allows one to perform the following operations effectively:
The Java implementation 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.
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)
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 in which the two criteria cannot be simultaneously satisfied, it may be possible to apply to notion of compound elements (see below).
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 one to assign a layer index to each element.
Once the model structure is given, one has to decide how the elements shall be displayed. The following possibilities exist:
In this case, 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 in which 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.
In this case, 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 an off-memory model is used.
In this case, 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 allows one 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.
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 one to replace the optimization algorithm on the fly, as well as to use new algorithms.
In GIS and some other applications, very big images consisting of millions of elements can be used. Storing the complete description of such images in virtual memory is not reasonable. A relational database or 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 an operation.
This approach can make standard optimization algorithms ineffective because they cannot utilize advanced features of the external storage such as database indexes. The Java implementation supports a concept of indexed data models that helps to deal with this problem. An 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 the features of the external storage.
The main advantages of the proposed design are the following:
This design was implemented in the Java language using the Swing GUI framework. The implementation is a JDrawing component. The documentation and source code of this component can be used as an additional source of information about the design described in this article.
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 an HTML image map.
OpenJGraph is an Open Source Java library for creating and manipulating graphs and graph drawings.
Its current features include:
Magelan provides a library of easy-to-use 2D graphics editor tools and a functional and extensible stand-alone graphics editor.
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 MS Excel does on a PC): histograms, plots, etc.
It was later extended, reducing the core of JChart to a reusable component and obtaining an applet and a stand-alone application as by-products. Data can be passed to it as a filename, as a data structure, or as a URL, and can be visualized in several ways.
Batik is a Java-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 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 interoperability.