~gabriel1984sibiu/minitube/qt5.6

« back to all changes in this revision

Viewing changes to examples/widgets/doc/src/elasticnodes.qdoc

  • Committer: Grevutiu Gabriel
  • Date: 2017-06-13 08:43:17 UTC
  • Revision ID: gabriel1984sibiu@gmail.com-20170613084317-ek0zqe0u9g3ocvi8
OriginalĀ upstreamĀ code

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 2016 The Qt Company Ltd.
 
4
** Contact: https://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 https://www.qt.io/terms-conditions. For further
 
15
** information use the contact form at https://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: https://www.gnu.org/licenses/fdl-1.3.html.
 
24
** $QT_END_LICENSE$
 
25
**
 
26
****************************************************************************/
 
27
 
 
28
/*!
 
29
    \example graphicsview/elasticnodes
 
30
    \title Elastic Nodes Example
 
31
    \ingroup examples-graphicsview
 
32
    \brief Demonstrates how to interact with graphical items in a scene
 
33
 
 
34
    The Elastic Nodes example shows how to implement edges between nodes in a
 
35
    graph, with basic interaction. 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
    \li Painting a yellow gradient "ball" in two states: sunken and raised.
 
60
    \li Managing connections to other nodes.
 
61
    \li 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 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 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 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 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 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 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 graphicsview/elasticnodes/node.cpp 5
 
155
 
 
156
    In theory, the sum of pushing and pulling forces should stabilize to
 
157
    precisely 0. In practice, 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 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 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 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 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 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 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 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 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 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 graphicsview/elasticnodes/edge.cpp 1
 
265
 
 
266
    The source and destination get-functions simply return the respective
 
267
    pointers.
 
268
 
 
269
    \snippet 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 practice 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 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 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 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 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 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 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 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 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 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 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 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 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 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
*/