Diagram Graphs and Pins
The wxArt2D library has fundamental ingredients for making complex graph drawings which are editable. A graph here is a set of canvas objects, which are connected directly or using wires. It has bin setup as flexible we could think of. So things like several flows in one drawing using classes of pins which are connect able to each other and not to others, is part of the design. Same for the tools which are used to edit a graph. Like the standard dragging tool, it is fully aware that objects can be part of a graph. And it makes sure that connected objects do stay connected, or that unconnected objects become connected at the right moment. This functionality of course has complicated the design of wxArt2D, but when not implementing it at the very base of the library in all aspects, it would have bin useless.
As always in wxArt2D, the objects are derived from a2dCanvasObject's, and for the wires the same applies, it is just a specialized a2dCanvasObject. The way to define how a canvas object is connected to a wire or other object, is by using a pin (a2dPin) at the position where the wire should connect to the canvas object. The pin is a child of the canvas object which is connected to the wire, and the wire has a pin child too, at the same position. Internal in the two pins the connection of the one pin to the other is made. This is no more then a pointer from the pin to the other and visa versa. Any canvas object can have pins as childs, and pins can connect, so all canvas objects can connect with each other directly or using a wire to indicate that two are more objects are connected. A wire like canvas object behaves differently in many occasions.
(picture object pin pin wire pin pin object and result next to it )
The main implementation of a wire object is currently in a2dCanvasWirePolylineL, but if needed other types of wires can be implented. Most easily by deriving from it, but you may also implement your own. As the name suggest, the wire is a a2dCanvasPolylineL derived object. The difference from a polyline with pins added as childs, is in the way it treats those pins and what happens if connected objects are dragged around. If for instance two non wire objects are dragged apart, automatically a wire will be created in between. There are several ways to reroute a wire when the object are dragged. The most simple one just make a straight wire between the new position of the begin and end pin, the more complex routing uses an algorithm, to make a new polyline which has 90 degree angles and routes as much as possible around existing other wires and objects.
Hierarchy in the form of grouped objects which can be itself grouped objects, is done by adding a2dCanvasObject's as children to a parent a2dCanvasObject. The top of this hierarchy is always the m_rootobject of a a2dCanvasDocument. So a2dPin's which are added to a parent a2dCanvasObject, is in principle no more then a simple hierarchy. You can add any other a2dCanvasObject as a child to the same parent. When the parent is a plain a2dCanvasObject, all that is rendered, are the children and pins. To make sure the pins are displayed on top of the other child objects, a flag is set to a pin, which results in rendering it later then the other children. The pin object itself is/can be rendered differently depending on it being connected or not. There are special feedback functions, which are used by (editing) tools, to change the mode of pin rendering depending on the possible connections at the point where the cursor position is. For instance when trying to create a new wire and connect it to an existing object, the object will give feedback by changing the rendering as long as the cursor is on top or near the pin on that object. Even if the object has no pins, like a standard rectangle, it can automatically create pins on the object. This we call dynamic pins. When connection is eventually made, the pin will remain in the object, else it will automatically be released. For most basic objects, this type of feedback is implemented, and for your own objects you can do the same. Still in case of a special designed library of connect able objects, one would mainly encounter pins at fixed positions within the objects. The CVG file format supports this, while dynamically created pins can only be implemented via a virtual function in C++ for that a2dCanvasObject. But as soon as dynamic pins are connected, they will be saved to CVG.
All the above is sufficient for creating simple graphs, like flows for programs, are any other graph where there are only connected objects, and all objects can connect to each other without restriction. In several situations more is needed. One might need a control and at the same time a data flow on the objects in the drawing, or certain types of objects can be connected while others can not. This functionality is achieved by adding a2dPinClass objects to a a2dPin. Certain pin classes may connect to each other while others may not, a2dConnectionGenerator implements a basic mechanism using an array of a2dPinClassMap's. But all its functions are virtual, so you can define any other ways of connecting objects. The a2dConnectionGenerator is asked to perform a specific task related to connecting objects, e.g to supply a new wire or a template for a new wire, given the object and the two pins which need to be connected by that wire. If NULL is returned, it is clear that those pin can not be connected. Those function are used within the library to automatically created new wires, and also by the tools to know which type of wire can start on which object etc. Interactive Feedback functions called from editing tools towards a2dCanvasObject's in the drawing, normally also use the a2dConnectionGenerator to find out if a connection is possible, and if the answer is positive, it while change its rendering mode accordingly. As said, a2dConnectionGenerator is generating new wires, therefore the way to make an application use different wire classes then the default a2dWirePolylineL, is by setting different wire templates to it.
A a2dCanvasObject can connect to other a2dCanvasObject, and a special type of a2dCanvasObject is a connect object ( with IsConnect() returning true ). Such an object is meant to be used as a connection object in between other normal objects. In General this is a wire like object. Currently the main implementation of such an object is a2dWirePolylineL. Let's call this object wire. A wire can be bidirectional or directional, or non directional. With this is meant that a wire can go only in certain direction, e.g. from an output to an input. Objects are connected to one another via a2dPin's ( in short pin ). A pin is added as a child to a parent a2dCanvasObject, and a pin itself can connect to one other pin inside another a2dCanvasObject. Connecting two pins is no more then setting a pointer in a pin to another, and doing the same in the second pin. Pins are allowed to connect to other pins on the basis of a2dPinClass objects. Each pin does have a a2dPinClass set to it. The a2dPinClass itself has a list of connectable a2dPinClass's. So when one wants to connect a pin to another pin, the two pinclasses in the pins must be connectable. Which is the case if the first pinclass has its connectlist the second and visa versa. Now the powerful part of all this, is that a a2dPin can easily be derived for whatever reason, since the whole connection strategy is separated from it in the from of a2dPinClass objects. Next those pinclass objects can act in groups of connectable pinclasses, which i call flows. A flow can be made directional using special pinclasses for in and out going pins on objects and wires.
Each a2dPinClass has a pointer to a a2dConnectionGenerator. Although a pinclass itself knows exactly which other pinclasses may connect to it, this is not enough to organize all needed. The connection generator does all the extra tasks, which can not be concentrated in one pinclass. Situations like this exist when two objects need to be connected via a wire. Also when automatically generating new pins in object, the connection generator tells what is possible. One application may have several derived a2dConnectionGenerator instances, for example: one for each flow.
A tool to draw wire inbetween objects is complex. One wants to start on a pin, but what wire is required for that pin? Or maybe one only wants to draw wires starting at certain types of pins. After starting a wire, one wants to know to which other pins connection is possible. Is it allowed to start wires on existing wires? This process requires feedback from pins in objects. The wire tool uses various feedback id to get informatiom from the document its objects and in there the pins.
We can recognize the following types of connection issues:
- An object or wire with fixed pins is ready for connecting if that pins allows connections. A feed back must be given to the tool when starting to draw a wire, which indicates that starting a wire on that pin is allowed.
- An object or wire which has by default has no wires, but which can generate them on the fly, must be able to generate the required pins, based on pinclasses. A pinclass alone is not enough, one needs to tell for what the pinclass is meant. Like the object needs to generate a pin with the given pinclass, or the object needs to generate a pin which can connect to the given pinclass, which is for example the start of a wire. The a2dConnectionGenerator is used, in combination with a pinclass and a task, to deliver a proper connecting pinclass.
- In case no specific wire needs to be created, any pin which allows to start a wire, can be created in an object. a2dPinClass::Any is a pinclass id, which is meant for that. The a2dConnectionGenerator attached to this pinclass decides what type of pin class will be used for new pins by default. One can decide different in each own made canvas object.
- If an object can have generated pins, and more then one type of wire can be connected on its pins, the type of wire and pins can only be decide after connecting the wire to another object. Although this is possible, it is better to design a group of pinclasses in such a manner that such things are not possible.
- New directional wires which start on an existing wire, always start at the begin of a directional wire. So the pinclass at the wire its begin pin, will be used.
generated pins are always temporary, and eventually will be removed again and if connection is made at that position anew non temporary pin will be added. Where temporary pins are generated within the object, depends upon the object ( see a2dCanvasObject::GeneratePins() ). A wire for instance must generate a pin at the spot where the mouse hits the wire. And if the mouse moves along the wire this pin must be regenerated again at the new spot. A rectangle only generates pins at fixed places.
- temporary pins are not added in an undoable mode. Only when a real connection is made, a new pin is added via undoable commands at that same position. The connection itself is also made via a command. This way wiring can be Undone and redone. pin generation can be different for connecting an object or wire to another object or wire. For that the a2dConnectionGenerator is given a task when searching for a possible pinclass.
Each user defined object can have automaticcally generated pins, in its a2dCanvasObject::GeneratePins(), those can be fixed in pinclass. But to be general it is best to have a2dConnectionGenerator decide the type of pinclass, and the object the position decides the new pin position. Even when pins have a fixed position, but the object can be used in more then one application, it may still be best to have the a2dConnectionGenerator decide what its pinclass will be.
When a wire tool needs to start a new wire on an object, there are three ways to do it:
- Pin inside the object under the mouse, decide what type of wire will be created. The pin its pinclass should have in its connectlist a pinclass which can be used on a wire its pin. Such a pinclass has a template object for a wire to be created on it.
- Tool wants the pin to be connectable to a specific pinclass, this pinclass is the start or begin of the wire the tool wants to draw. The object should feedback all pins which can be conencted to the given pinclass.
- More then one type of wire can start on a pin. In this case one needs to wait until the end of the wire has bin choosen. If the pinclass at the end of the wire is knows, then the type of wire is clear, and so is the begin pinclass.
Steps and feedback needed when drawing a wire are:
- feedback on possible pins which can be connected. For existing pins, the should render as connectable, for generated pins in this feedback the same should be done.
- Feedback on start pin, can be for input and output of directional wires. If an object input pinclass was choosen, the wire will be drawn in reverse direction.
- After a hit on a connectable pin, feadback should indicates that connection is possible and a pin is close enough to conenct.
- After a hit and Mouse Left Down, the wire starts, and pin is rendered connected (pin becomes invisible ).
- The wire end position is the mouse position, and needs a feedback from objects when it can connect to a pin inside those object. These pins will be generated if needed.
A typical connection implementation is when objects on a layer can be connected to other objects on the same layer, or to a fixed set of layers. It maybe also depend on the object its pins to which objects on the same or other layers it may connect. So how would one implement it using the a2dPinClass system. For each layer you define a pin class. Objects placed on the layer, get by default a pin with the layer pinclass. Say they are called PinClassLayerXX where XX is unique for each layer. To a PinClassLayer01 you can add for example connection pin classes PinClassLayer01, PinClassLayer02 and PinClassLayer03. A pin on another object with pinclass PinClassLayer03 and in its connection table PinClassLayer01 may connect to the first pin, since they both have in their connection list the other pin its pinclass. The above has in fact nothing to do with layers directly, only the fact that one uses for objects on a certain layer always the same PinClassLayerXX, makes it seem that all the objects on the layer behave the same way when it comes to connecting to other objects. But if you want some objects to behave different anyway, you can do so. In order to make object coming from library of objects more general, the a2dLayerInfo which defines the properties of a layer in wxArt2D, also may have set a a2dPinClass set to it. This pin class will be the default for object placed on the layer. The default implementation of a2dConnectionGenerator uses this pinclass when generating pins automatically. You just set per layer the default pinclass you would like to use, and all standard objects like rectangles and such will generate pins based on those pinclasses.