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