Update copyright headers
[qt:qt.git] / doc / src / examples / elasticnodes.qdoc
1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the documentation of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:FDL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Free Documentation License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Free
19 ** Documentation License version 1.3 as published by the Free Software
20 ** Foundation and appearing in the file included in the packaging of
21 ** this file.  Please review the following information to ensure
22 ** the GNU Free Documentation License version 1.3 requirements
23 ** will be met: http://www.gnu.org/copyleft/fdl.html.
24 ** $QT_END_LICENSE$
25 **
26 ****************************************************************************/
27
28 /*!
29     \example graphicsview/elasticnodes
30     \title Elastic Nodes Example
31
32     \brief The Elastic Nodes example shows how to implement edges between nodes in a
33     graph, with basic interaction.
34     
35     You can click to drag a node around, and
36     zoom in and out using the mouse wheel or the keyboard. Hitting the space
37     bar will randomize the nodes. The example is also resolution independent;
38     as you zoom in, the graphics remain crisp.
39
40     \image elasticnodes-example.png
41
42     Graphics View provides the QGraphicsScene class for managing and
43     interacting with a large number of custom-made 2D graphical items derived
44     from the QGraphicsItem class, and a QGraphicsView widget for visualizing
45     the items, with support for zooming and rotation.
46
47     This example consists of a \c Node class, an \c Edge class, a \c
48     GraphWidget test, and a \c main function: the \c Node class represents
49     draggable yellow nodes in a grid, the \c Edge class represents the lines
50     between the nodes, the \c GraphWidget class represents the application
51     window, and the \c main() function creates and shows this window, and runs
52     the event loop.
53
54     \section1 Node Class Definition
55
56     The \c Node class serves three purposes:
57
58     \list
59     \o Painting a yellow gradient "ball" in two states: sunken and raised.
60     \o Managing connections to other nodes.
61     \o Calculating forces pulling and pushing the nodes in the grid.
62     \endlist
63
64     Let's start by looking at the \c Node class declaration.
65
66     \snippet examples/graphicsview/elasticnodes/node.h 0
67
68     The \c Node class inherits QGraphicsItem, and reimplements the two
69     mandatory functions \l{QGraphicsItem::boundingRect()}{boundingRect()} and
70     \l{QGraphicsItem::paint()}{paint()} to provide its visual appearance. It
71     also reimplements \l{QGraphicsItem::shape()}{shape()} to ensure its hit
72     area has an elliptic shape (as opposed to the default bounding rectangle).
73
74     For edge management purposes, the node provides a simple API for adding
75     edges to a node, and for listing all connected edges.
76
77     The \l{QGraphicsItem::advance()}{advance()} reimplementation is called
78     whenever the scene's state advances by one step. The calculateForces()
79     function is called to calculate the forces that push and pull on this node
80     and its neighbors.
81
82     The \c Node class also reimplements
83     \l{QGraphicsItem::itemChange()}{itemChange()} to react to state changes (in
84     this case, position changes), and
85     \l{QGraphicsItem::mousePressEvent()}{mousePressEvent()} and
86     \l{QGraphicsItem::mouseReleaseEvent()}{mouseReleaseEvent()} to update the
87     item's visual appearance.
88
89     We will start reviewing the \c Node implementation by looking at its
90     constructor:
91
92     \snippet examples/graphicsview/elasticnodes/node.cpp 0
93
94     In the constructor, we set the
95     \l{QGraphicsItem::ItemIsMovable}{ItemIsMovable} flag to allow the item to
96     move in response to mouse dragging, and
97     \l{QGraphicsItem::ItemSendsGeometryChanges}{ItemSendsGeometryChanges} to
98     enable \l{QGraphicsItem::itemChange()}{itemChange()} notifications for
99     position and transformation changes. We also enable
100     \l{QGraphicsItem::DeviceCoordinateCache}{DeviceCoordinateCache} to speed up
101     rendering performance. To ensure that the nodes are always stacked on top
102     of edges, we finally set the item's Z value to -1.
103
104     \c Node's constructor takes a \c GraphWidget pointer and stores this as a
105     member variable. We will revisit this pointer later on.
106
107     \snippet examples/graphicsview/elasticnodes/node.cpp 1
108
109     The addEdge() function adds the input edge to a list of attached edges. The
110     edge is then adjusted so that the end points for the edge match the
111     positions of the source and destination nodes.
112
113     The edges() function simply returns the list of attached edges.
114
115     \snippet examples/graphicsview/elasticnodes/node.cpp 2
116
117     There are two ways to move a node. The \c calculateForces() function
118     implements the elastic effect that pulls and pushes on nodes in the grid.
119     In addition, the user can directly move one node around with the mouse.
120     Because we do not want the two approaches to operate at the same time on
121     the same node, we start \c calculateForces() by checking if this \c Node is
122     the current mouse grabber item (i.e., QGraphicsScene::mouseGrabberItem()).
123     Because we need to find all neighboring (but not necessarily connected)
124     nodes, we also make sure the item is part of a scene in the first place.
125
126     \snippet examples/graphicsview/elasticnodes/node.cpp 3
127
128     The "elastic" effect comes from an algorithm that applies pushing and
129     pulling forces. The effect is impressive, and surprisingly simple to
130     implement.
131
132     The algorithm has two steps: the first is to calculate the forces that push
133     the nodes apart, and the second is to subtract the forces that pull the
134     nodes together. First we need to find all the nodes in the graph. We call
135     QGraphicsScene::items() to find all items in the scene, and then use
136     qgraphicsitem_cast() to look for \c Node instances.
137
138     We make use of \l{QGraphicsItem::mapFromItem()}{mapFromItem()} to create a
139     temporary vector pointing from this node to each other node, in \l{The
140     Graphics View Coordinate System}{local coordinates}. We use the decomposed
141     components of this vector to determine the direction and strength of force
142     that should apply to the node. The forces accumulate for each node, and are
143     then adjusted so that the closest nodes are given the strongest force, with
144     rapid degradation when distance increases. The sum of all forces is stored
145     in \c xvel (X-velocity) and \c yvel (Y-velocity).
146
147     \snippet examples/graphicsview/elasticnodes/node.cpp 4
148
149     The edges between the nodes represent forces that pull the nodes together.
150     By visiting each edge that is connected to this node, we can use a similar
151     approach as above to find the direction and strength of all pulling forces.
152     These forces are subtracted from \c xvel and \c yvel.
153
154     \snippet examples/graphicsview/elasticnodes/node.cpp 5
155
156     In theory, the sum of pushing and pulling forces should stabilize to
157     precisely 0. In practise, however, they never do. To circumvent errors in
158     numerical precision, we simply force the sum of forces to be 0 when they
159     are less than 0.1.
160
161     \snippet examples/graphicsview/elasticnodes/node.cpp 6
162
163     The final step of \c calculateForces() determines the node's new position.
164     We add the force to the node's current position. We also make sure the new
165     position stays inside of our defined boundaries. We don't actually move the
166     item in this function; that's done in a separate step, from \c advance().
167
168     \snippet examples/graphicsview/elasticnodes/node.cpp 7
169
170     The \c advance() function updates the item's current position. It is called
171     from \c GraphWidget::timerEvent(). If the node's position changed, the
172     function returns true; otherwise false is returned.
173
174     \snippet examples/graphicsview/elasticnodes/node.cpp 8
175
176     The \c Node's bounding rectangle is a 20x20 sized rectangle centered around
177     its origin (0, 0), adjusted by 2 units in all directions to compensate for
178     the node's outline stroke, and by 3 units down and to the right to make
179     room for a simple drop shadow.
180
181     \snippet examples/graphicsview/elasticnodes/node.cpp 9
182
183     The shape is a simple ellipse. This ensures that you must click inside the
184     node's elliptic shape in order to drag it around. You can test this effect
185     by running the example, and zooming far in so that the nodes are very
186     large. Without reimplementing \l{QGraphicsItem::shape()}{shape()}, the
187     item's hit area would be identical to its bounding rectangle (i.e.,
188     rectangular).
189
190     \snippet examples/graphicsview/elasticnodes/node.cpp 10
191
192     This function implements the node's painting. We start by drawing a simple
193     dark gray elliptic drop shadow at (-7, -7), that is, (3, 3) units down and
194     to the right from the top-left corner (-10, -10) of the ellipse.
195
196     We then draw an ellipse with a radial gradient fill. This fill is either
197     Qt::yellow to Qt::darkYellow when raised, or the opposite when sunken. In
198     sunken state we also shift the center and focal point by (3, 3) to
199     emphasize the impression that something has been pushed down.
200
201     Drawing filled ellipses with gradients can be quite slow, especially when
202     using complex gradients such as QRadialGradient. This is why this example
203     uses \l{QGraphicsItem::DeviceCoordinateCache}{DeviceCoordinateCache}, a
204     simple yet effective measure that prevents unnecessary redrawing.
205
206     \snippet examples/graphicsview/elasticnodes/node.cpp 11
207
208     We reimplement \l{QGraphicsItem::itemChange()}{itemChange()} to adjust the
209     position of all connected edges, and to notify the scene that an item has
210     moved (i.e., "something has happened"). This will trigger new force
211     calculations.
212
213     This notification is the only reason why the nodes need to keep a pointer
214     back to the \c GraphWidget. Another approach could be to provide such
215     notification using a signal; in such case, \c Node would need to inherit
216     from QGraphicsObject.
217
218     \snippet examples/graphicsview/elasticnodes/node.cpp 12
219
220     Because we have set the \l{QGraphicsItem::ItemIsMovable}{ItemIsMovable}
221     flag, we don't need to implement the logic that moves the node according to
222     mouse input; this is already provided for us. We still need to reimplement
223     the mouse press and release handlers, though, to update the nodes' visual
224     appearance (i.e., sunken or raised).
225
226     \section1 Edge Class Definition
227
228     The \c Edge class represents the arrow-lines between the nodes in this
229     example. The class is very simple: it maintains a source- and destination
230     node pointer, and provides an \c adjust() function that makes sure the line
231     starts at the position of the source, and ends at the position of the
232     destination. The edges are the only items that change continuously as
233     forces pull and push on the nodes.
234
235     Let's take a look at the class declaration:
236
237     \snippet examples/graphicsview/elasticnodes/edge.h 0
238
239     \c Edge inherits from QGraphicsItem, as it's a simple class that has no use
240     for signals, slots, and properties (compare to QGraphicsObject).
241
242     The constructor takes two node pointers as input. Both pointers are
243     mandatory in this example. We also provide get-functions for each node.
244
245     The \c adjust() function repositions the edge, and the item also implements
246     \l{QGraphicsItem::boundingRect()}{boundingRect()} and
247     \l{QGraphicsItem::paint()}{paint()}.
248
249     We will now review its implementation.
250
251     \snippet examples/graphicsview/elasticnodes/edge.cpp 0
252
253     The \c Edge constructor initializes its \c arrowSize data member to 10 units;
254     this determines the size of the arrow which is drawn in
255     \l{QGraphicsItem::paint()}{paint()}.
256
257     In the constructor body, we call
258     \l{QGraphicsItem::setAcceptedMouseButtons()}{setAcceptedMouseButtons(0)}.
259     This ensures that the edge items are not considered for mouse input at all
260     (i.e., you cannot click the edges). Then, the source and destination
261     pointers are updated, this edge is registered with each node, and we call
262     \c adjust() to update this edge's start end end position.
263
264     \snippet examples/graphicsview/elasticnodes/edge.cpp 1
265
266     The source and destination get-functions simply return the respective
267     pointers.
268
269     \snippet examples/graphicsview/elasticnodes/edge.cpp 2
270
271     In \c adjust(), we define two points: \c sourcePoint, and \c destPoint,
272     pointing at the source and destination nodes' origins respectively. Each
273     point is calculated using \l{The Graphics View Coordinate System}{local
274     coordinates}.
275
276     We want the tip of the edge's arrows to point to the exact outline of the
277     nodes, as opposed to the center of the nodes. To find this point, we first
278     decompose the vector pointing from the center of the source to the center
279     of the destination node into X and Y, and then normalize the components by
280     dividing by the length of the vector. This gives us an X and Y unit delta
281     that, when multiplied by the radius of the node (which is 10), gives us the
282     offset that must be added to one point of the edge, and subtracted from the
283     other.
284
285     If the length of the vector is less than 20 (i.e., if two nodes overlap),
286     then we fix the source and destination pointer at the center of the source
287     node. In practise this case is very hard to reproduce manually, as the
288     forces between the two nodes is then at its maximum.
289
290     It's important to notice that we call
291     \l{QGraphicsItem::prepareGeometryChange()}{prepareGeometryChange()} in this
292     function. The reason is that the variables \c sourcePoint and \c destPoint
293     are used directly when painting, and they are returned from the
294     \l{QGraphicsItem::boundingRect()}{boundingRect()} reimplementation. We must
295     always call
296     \l{QGraphicsItem::prepareGeometryChange()}{prepareGeometryChange()} before
297     changing what \l{QGraphicsItem::boundingRect()}{boundingRect()} returns,
298     and before these variables can be used by
299     \l{QGraphicsItem::paint()}{paint()}, to keep Graphics View's internal
300     bookkeeping clean. It's safest to call this function once, immediately
301     before any such variable is modified.
302
303     \snippet examples/graphicsview/elasticnodes/edge.cpp 3
304
305     The edge's bounding rectangle is defined as the smallest rectangle that
306     includes both the start and the end point of the edge. Because we draw an
307     arrow on each edge, we also need to compensate by adjusting with half the
308     arrow size and half the pen width in all directions. The pen is used to
309     draw the outline of the arrow, and we can assume that half of the outline
310     can be drawn outside of the arrow's area, and half will be drawn inside.
311
312     \snippet examples/graphicsview/elasticnodes/edge.cpp 4
313
314     We start the reimplementation of \l{QGraphicsItem::paint()}{paint()} by
315     checking a few preconditions. Firstly, if either the source or destination
316     node is not set, then we return immediately; there is nothing to draw.
317
318     At the same time, we check if the length of the edge is approximately 0,
319     and if it is, then we also return.
320
321     \snippet examples/graphicsview/elasticnodes/edge.cpp 5
322
323     We draw the line using a pen that has round joins and caps. If you run the
324     example, zoom in and study the edge in detail, you will see that there are
325     no sharp/square edges.
326
327     \snippet examples/graphicsview/elasticnodes/edge.cpp 6
328
329     We proceed to drawing one arrow at each end of the edge. Each arrow is
330     drawn as a polygon with a black fill. The coordinates for the arrow are
331     determined using simple trigonometry.
332
333     \section1 GraphWidget Class Definition
334
335     \c GraphWidget is a subclass of QGraphicsView, which provides the main
336     window with scrollbars.
337
338     \snippet examples/graphicsview/elasticnodes/graphwidget.h 0
339
340     The class provides a basic constructor that initializes the scene, an \c
341     itemMoved() function to notify changes in the scene's node graph, a few
342     event handlers, a reimplementation of
343     \l{QGraphicsView::drawBackground()}{drawBackground()}, and a helper
344     function for scaling the view by using the mouse wheel or keyboard.
345
346     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 0
347
348     \c GraphicsWidget's constructor creates the scene, and because most items
349     move around most of the time, it sets QGraphicsScene::NoIndex. The scene
350     then gets a fixed \l{QGraphicsScene::sceneRect}{scene rectangle}, and is
351     assigned to the \c GraphWidget view.
352
353     The view enables QGraphicsView::CacheBackground to cache rendering of its
354     static, and somewhat complex, background. Because the graph renders a close
355     collection of small items that all move around, it's unnecessary for
356     Graphics View to waste time finding accurate update regions, so we set the
357     QGraphicsView::BoundingRectViewportUpdate viewport update mode. The default
358     would work fine, but this mode is noticably faster for this example.
359
360     To improve rendering quality, we set QPainter::Antialiasing.
361
362     The transformation anchor decides how the view should scroll when you
363     transform the view, or in our case, when we zoom in or out. We have chosen
364     QGraphicsView::AnchorUnderMouse, which centers the view on the point under
365     the mouse cursor. This makes it easy to zoom towards a point in the scene
366     by moving the mouse over it, and then rolling the mouse wheel.
367
368     Finally we give the window a minimum size that matches the scene's default
369     size, and set a suitable window title.
370
371     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 1
372
373     The last part of the constructor creates the grid of nodes and edges, and
374     gives each node an initial position.
375
376     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 2
377
378     \c GraphWidget is notified of node movement through this \c itemMoved()
379     function. Its job is simply to restart the main timer in case it's not
380     running already. The timer is designed to stop when the graph stabilizes,
381     and start once it's unstable again.
382
383     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 3
384
385     This is \c GraphWidget's key event handler. The arrow keys move the center
386     node around, the '+' and '-' keys zoom in and out by calling \c
387     scaleView(), and the enter and space keys randomize the positions of the
388     nodes. All other key events (e.g., page up and page down) are handled by
389     QGraphicsView's default implementation.
390
391     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 4
392
393     The timer event handler's job is to run the whole force calculation
394     machinery as a smooth animation. Each time the timer is triggered, the
395     handler will find all nodes in the scene, and call \c
396     Node::calculateForces() on each node, one at a time. Then, in a final step
397     it will call \c Node::advance() to move all nodes to their new positions.
398     By checking the return value of \c advance(), we can decide if the grid
399     stabilized (i.e., no nodes moved). If so, we can stop the timer.
400
401     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 5
402
403     In the wheel event handler, we convert the mouse wheel delta to a scale
404     factor, and pass this factor to \c scaleView(). This approach takes into
405     account the speed that the wheel is rolled. The faster you roll the mouse
406     wheel, the faster the view will zoom.
407
408     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 6
409
410     The view's background is rendered in a reimplementation of
411     QGraphicsView::drawBackground(). We draw a large rectangle filled with a
412     linear gradient, add a drop shadow, and then render text on top. The text
413     is rendered twice for a simple drop-shadow effect.
414
415     This background rendering is quite expensive; this is why the view enables
416     QGraphicsView::CacheBackground.
417
418     \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 7
419
420     The \c scaleView() helper function checks that the scale factor stays
421     within certain limits (i.e., you cannot zoom too far in nor too far out),
422     and then applies this scale to the view.
423
424     \section1 The main() Function
425
426     In contrast to the complexity of the rest of this example, the \c main()
427     function is very simple: We create a QApplication instance, seed the
428     randomizer using qsrand(), and then create and show an instance of \c
429     GraphWidget. Because all nodes in the grid are moved initially, the \c
430     GraphWidget timer will start immediately after control has returned to the
431     event loop.
432 */