KD Chart 2  [rev.2.7]
KDChartChart.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2020 Klaralvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Chart library.
5 **
6 ** Licensees holding valid commercial KD Chart licenses may use this file in
7 ** accordance with the KD Chart Commercial License Agreement provided with
8 ** the Software.
9 **
10 **
11 ** This file may be distributed and/or modified under the terms of the
12 ** GNU General Public License version 2 and version 3 as published by the
13 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
14 **
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 **
18 ** Contact info@kdab.com if any conditions of this licensing are not
19 ** clear to you.
20 **
21 **********************************************************************/
22 
23 #include "KDChartChart.h"
24 #include "KDChartChart_p.h"
25 
26 #include <QList>
27 #include <QtDebug>
28 #include <QGridLayout>
29 #include <QLabel>
30 #include <QHash>
31 #include <QToolTip>
32 #include <QPainter>
33 #include <QPaintEvent>
34 #include <QLayoutItem>
35 #include <QPushButton>
36 #include <QApplication>
37 #include <QEvent>
38 
41 #include "KDChartHeaderFooter.h"
42 #include "KDChartEnums.h"
43 #include "KDChartLegend.h"
44 #include "KDChartLayoutItems.h"
45 #include <KDChartTextAttributes.h>
47 #include "KDChartPainterSaver_p.h"
49 
50 #include <algorithm>
51 
52 #if defined KDAB_EVAL
53 #include "../evaldialog/evaldialog.h"
54 #endif
55 
56 #include <KDABLibFakes>
57 
58 #if 0
59 // dumpLayoutTree dumps a QLayout tree in a hopefully easy to read format to stderr - feel free to
60 // use, improve and extend; it is very useful for looking at any layout problem.
61 
62 #include <typeinfo>
63 
64 // this is this different from both QRect::isEmpty() and QRect::isNull() for "wrong" QRects,
65 // i.e. those where topLeft() is actually below and / or right of bottomRight().
66 static bool isZeroArea(const QRect &r)
67 {
68  return !r.width() || !r.height();
69 }
70 
71 static QString lineProlog(int nestingDepth, int lineno)
72 {
73  QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(':')));
74  QString indent(nestingDepth * 4, QLatin1Char(' '));
75  return numbering + indent;
76 }
77 
78 static void dumpLayoutTreeRecurse(QLayout *l, int *counter, int depth)
79 {
80  const QLatin1String colorOn(isZeroArea(l->geometry()) ? "\033[0m" : "\033[32m");
81  const QLatin1String colorOff("\033[0m");
82 
83  QString prolog = lineProlog(depth, *counter);
84  (*counter)++;
85 
86  qDebug() << colorOn + prolog << l->metaObject()->className() << l->geometry()
87  << "hint" << l->sizeHint()
88  << l->hasHeightForWidth() << "min" << l->minimumSize()
89  << "max" << l->maximumSize()
90  << l->expandingDirections() << l->alignment()
91  << colorOff;
92  for (int i = 0; i < l->count(); i++) {
93  QLayoutItem *child = l->itemAt(i);
94  if (QLayout *childL = child->layout()) {
95  dumpLayoutTreeRecurse(childL, counter, depth + 1);
96  } else {
97  // The isZeroArea check culls usually largely useless output - you might want to remove it in
98  // some debugging situations. Add a boolean parameter to this and dumpLayoutTree() if you do.
99  if (!isZeroArea(child->geometry())) {
100  prolog = lineProlog(depth + 1, *counter);
101  (*counter)++;
102  qDebug() << colorOn + prolog << typeid(*child).name() << child->geometry()
103  << "hint" << child->sizeHint()
104  << child->hasHeightForWidth() << "min" << child->minimumSize()
105  << "max" << child->maximumSize()
106  << child->expandingDirections() << child->alignment()
107  << colorOff;
108  }
109  }
110  }
111 }
112 
113 static void dumpLayoutTree(QLayout *l)
114 {
115  int counter = 0;
116  dumpLayoutTreeRecurse(l, &counter, 0);
117 }
118 #endif
119 
120 static const Qt::Alignment s_gridAlignments[ 3 ][ 3 ] = { // [ row ][ column ]
121  { Qt::AlignTop | Qt::AlignLeft, Qt::AlignTop | Qt::AlignHCenter, Qt::AlignTop | Qt::AlignRight },
122  { Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight },
123  { Qt::AlignBottom | Qt::AlignLeft, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignBottom | Qt::AlignRight }
124 };
125 
126 static void getRowAndColumnForPosition(KDChartEnums::PositionValue pos, int* row, int* column)
127 {
128  switch ( pos ) {
129  case KDChartEnums::PositionNorthWest: *row = 0; *column = 0;
130  break;
131  case KDChartEnums::PositionNorth: *row = 0; *column = 1;
132  break;
133  case KDChartEnums::PositionNorthEast: *row = 0; *column = 2;
134  break;
135  case KDChartEnums::PositionEast: *row = 1; *column = 2;
136  break;
137  case KDChartEnums::PositionSouthEast: *row = 2; *column = 2;
138  break;
139  case KDChartEnums::PositionSouth: *row = 2; *column = 1;
140  break;
141  case KDChartEnums::PositionSouthWest: *row = 2; *column = 0;
142  break;
143  case KDChartEnums::PositionWest: *row = 1; *column = 0;
144  break;
145  case KDChartEnums::PositionCenter: *row = 1; *column = 1;
146  break;
147  default: *row = -1; *column = -1;
148  break;
149  }
150 }
151 
152 using namespace KDChart;
153 
154 // Layout widgets even if they are not visible (that's why isEmpty() is overridden) - at least that
155 // was the original reason...
156 class MyWidgetItem : public QWidgetItem
157 {
158 public:
159  explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = 0)
160  : QWidgetItem( w )
161  {
162  setAlignment( alignment );
163  }
164 
165  // All of the methods are reimplemented from QWidgetItem, and work around some oddity in QLayout and / or
166  // KD Chart - I forgot the details between writing this code as an experiment and committing it, very
167  // sorry about that!
168  // Feel free to comment out any of them and then try the line-breaking feature in horizontal legends in
169  // the Legends/Advanced example. It will not work well in various ways - won't get enough space and look
170  // very broken, will inhibit resizing the window etc.
171 
172  QSize sizeHint() const override
173  {
174  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
175  return w->sizeHint();
176  }
177 
178  QSize minimumSize() const override
179  {
180  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
181  return w->minimumSize();
182  }
183 
184  QSize maximumSize() const override
185  {
186  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
187  return w->maximumSize();
188  }
189 
190  Qt::Orientations expandingDirections() const override
191  {
192  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
193  if ( isEmpty() ) {
194  return Qt::Orientations(0);
195  }
196  Qt::Orientations e = w->sizePolicy().expandingDirections();
197  return e;
198  }
199 
200  void setGeometry(const QRect &g) override
201  {
202  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
203  w->setGeometry(g);
204  }
205 
206  QRect geometry() const override
207  {
208  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
209  return w->geometry();
210  }
211 
212  bool hasHeightForWidth() const override
213  {
214  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
215  bool ret = !isEmpty() &&
216  qobject_cast< Legend* >( w )->hasHeightForWidth();
217  return ret;
218  }
219 
220  int heightForWidth( int width ) const override
221  {
222  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
223  int ret = w->heightForWidth( width );
224  return ret;
225  }
226 
227  bool isEmpty() const override {
228  QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
229  // legend->hide() should indeed hide the legend,
230  // but a legend in a chart that hasn't been shown yet isn't hidden
231  // (as can happen when using Chart::paint() without showing the chart)
232  return w->isHidden() && w->testAttribute( Qt::WA_WState_ExplicitShowHide );
233  }
234 };
235 
236 // When "abusing" QLayouts to lay out items with different geometry from the backing QWidgets,
237 // some manual work is required to correctly update all the sublayouts.
238 // This is because all the convenient ways to deal with QLayouts assume QWidgets somewhere.
239 // What this does is somewhat similar to QLayout::activate(), but it never refers to the parent
240 // QWidget which has the wrong geometry.
241 static void invalidateLayoutTree( QLayoutItem *item )
242 {
243  QLayout *layout = item->layout();
244  if ( layout ) {
245  const int count = layout->count();
246  for ( int i = 0; i < count; i++ ) {
247  invalidateLayoutTree( layout->itemAt( i ) );
248  }
249  }
250  item->invalidate();
251 }
252 
253 void Chart::Private::slotUnregisterDestroyedLegend( Legend *l )
254 {
255  chart->takeLegend( l );
256 }
257 
258 void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf )
259 {
260  chart->takeHeaderFooter( hf );
261 }
262 
263 void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane )
264 {
265  coordinatePlanes.removeAll( plane );
266  Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) {
267  if ( p->referenceCoordinatePlane() == plane) {
269  }
270  }
271  plane->layoutPlanes();
272 }
273 
274 Chart::Private::Private( Chart* chart_ )
275  : chart( chart_ )
276  , useNewLayoutSystem( false )
277  , layout( 0 )
278  , vLayout( 0 )
279  , planesLayout( 0 )
280  , headerLayout( 0 )
281  , footerLayout( 0 )
282  , dataAndLegendLayout( 0 )
283  , leftOuterSpacer( 0 )
284  , rightOuterSpacer( 0 )
285  , topOuterSpacer( 0 )
286  , bottomOuterSpacer( 0 )
287  , isFloatingLegendsLayoutDirty( true )
288  , isPlanesLayoutDirty( true )
289  , globalLeadingLeft( 0 )
290  , globalLeadingRight( 0 )
291  , globalLeadingTop( 0 )
292  , globalLeadingBottom( 0 )
293 {
294  for ( int row = 0; row < 3; ++row ) {
295  for ( int column = 0; column < 3; ++column ) {
296  for ( int i = 0; i < 2; i++ ) {
297  innerHdFtLayouts[ i ][ row ][ column ] = 0;
298  }
299  }
300  }
301 }
302 
303 Chart::Private::~Private()
304 {
305 }
306 
308 struct ConnectedComponentsComparator{
309  bool operator()( const LayoutGraphNode *lhs, const LayoutGraphNode *rhs ) const
310  {
311  return lhs->priority < rhs->priority;
312  }
313 };
314 
316 {
317  QVector< LayoutGraphNode* >connectedComponents;
318  QHash< LayoutGraphNode*, VisitorState > visitedComponents;
319  Q_FOREACH ( LayoutGraphNode* node, nodeList )
320  visitedComponents[ node ] = Unknown;
321  for ( int i = 0; i < nodeList.size(); ++i )
322  {
323  LayoutGraphNode *curNode = nodeList[ i ];
324  LayoutGraphNode *representativeNode = curNode;
325  if ( visitedComponents[ curNode ] != Visited )
326  {
327  QStack< LayoutGraphNode* > stack;
328  stack.push( curNode );
329  while ( !stack.isEmpty() )
330  {
331  curNode = stack.pop();
332  Q_ASSERT( visitedComponents[ curNode ] != Visited );
333  visitedComponents[ curNode ] = Visited;
334  if ( curNode->bottomSuccesor && visitedComponents[ curNode->bottomSuccesor ] != Visited )
335  stack.push( curNode->bottomSuccesor );
336  if ( curNode->leftSuccesor && visitedComponents[ curNode->leftSuccesor ] != Visited )
337  stack.push( curNode->leftSuccesor );
338  if ( curNode->sharedSuccesor && visitedComponents[ curNode->sharedSuccesor ] != Visited )
339  stack.push( curNode->sharedSuccesor );
340  if ( curNode->priority < representativeNode->priority )
341  representativeNode = curNode;
342  }
343  connectedComponents.append( representativeNode );
344  }
345  }
346  std::sort( connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator() );
347  return connectedComponents;
348 }
349 
350 struct PriorityComparator{
351 public:
352  PriorityComparator( QHash< AbstractCoordinatePlane*, LayoutGraphNode* > mapping )
353  : m_mapping( mapping )
354  {}
355  bool operator() ( AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs ) const
356  {
357  const LayoutGraphNode *lhsNode = m_mapping[ lhs ];
358  Q_ASSERT( lhsNode );
359  const LayoutGraphNode *rhsNode = m_mapping[ rhs ];
360  Q_ASSERT( rhsNode );
361  return lhsNode->priority < rhsNode->priority;
362  }
363 
364  const QHash< AbstractCoordinatePlane*, LayoutGraphNode* > m_mapping;
365 };
366 
367 void checkExistingAxes( LayoutGraphNode* node )
368 {
369  if ( node && node->diagramPlane && node->diagramPlane->diagram() )
370  {
371  AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( node->diagramPlane->diagram() );
372  if ( diag )
373  {
374  Q_FOREACH( const CartesianAxis* axis, diag->axes() )
375  {
376  switch ( axis->position() )
377  {
378  case( CartesianAxis::Top ):
379  node->topAxesLayout = true;
380  break;
381  case( CartesianAxis::Bottom ):
382  node->bottomAxesLayout = true;
383  break;
384  case( CartesianAxis::Left ):
385  node->leftAxesLayout = true;
386  break;
387  case( CartesianAxis::Right ):
388  node->rightAxesLayout = true;
389  break;
390  }
391  }
392  }
393  }
394 }
395 
396 static void mergeNodeAxisInformation( LayoutGraphNode* lhs, LayoutGraphNode* rhs )
397 {
398  lhs->topAxesLayout |= rhs->topAxesLayout;
399  rhs->topAxesLayout = lhs->topAxesLayout;
400 
401  lhs->bottomAxesLayout |= rhs->bottomAxesLayout;
402  rhs->bottomAxesLayout = lhs->bottomAxesLayout;
403 
404  lhs->leftAxesLayout |= rhs->leftAxesLayout;
405  rhs->leftAxesLayout = lhs->leftAxesLayout;
406 
407  lhs->rightAxesLayout |= rhs->rightAxesLayout;
408  rhs->rightAxesLayout = lhs->rightAxesLayout;
409 }
410 
412  const CoordinatePlaneList& list,
413  Chart::Private::AxisType type,
414  QVector< CartesianAxis* >* sharedAxes )
415 {
416  if ( !plane || !plane->diagram() )
417  return CoordinatePlaneList();
418  Q_ASSERT( plane );
419  Q_ASSERT( plane->diagram() );
420  CoordinatePlaneList result;
421  AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( plane->diagram() );
422  if ( !diagram )
423  return CoordinatePlaneList();
424 
426  KDAB_FOREACH( CartesianAxis* axis, diagram->axes() ) {
427  if ( ( type == Chart::Private::Ordinate &&
428  ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) )
429  ||
430  ( type == Chart::Private::Abscissa &&
431  ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) ) ) {
432  axes.append( axis );
433  }
434  }
435  Q_FOREACH( AbstractCoordinatePlane *curPlane, list )
436  {
437  AbstractCartesianDiagram* diagram =
438  qobject_cast< AbstractCartesianDiagram* > ( curPlane->diagram() );
439  if ( !diagram )
440  continue;
441  Q_FOREACH( CartesianAxis* curSearchedAxis, axes )
442  {
443  Q_FOREACH( CartesianAxis* curAxis, diagram->axes() )
444  {
445  if ( curSearchedAxis == curAxis )
446  {
447  result.append( curPlane );
448  if ( !sharedAxes->contains( curSearchedAxis ) )
449  sharedAxes->append( curSearchedAxis );
450  }
451  }
452  }
453  }
454 
455  return result;
456 }
457 
464 QVector< LayoutGraphNode* > Chart::Private::buildPlaneLayoutGraph()
465 {
466  QHash< AbstractCoordinatePlane*, LayoutGraphNode* > planeNodeMapping;
468  // create all nodes and a mapping between plane and nodes
469  Q_FOREACH( AbstractCoordinatePlane* curPlane, coordinatePlanes )
470  {
471  if ( curPlane->diagram() )
472  {
473  allNodes.append( new LayoutGraphNode );
474  allNodes[ allNodes.size() - 1 ]->diagramPlane = curPlane;
475  allNodes[ allNodes.size() - 1 ]->priority = allNodes.size();
476  checkExistingAxes( allNodes[ allNodes.size() - 1 ] );
477  planeNodeMapping[ curPlane ] = allNodes[ allNodes.size() - 1 ];
478  }
479  }
480  // build the graph connections
481  Q_FOREACH( LayoutGraphNode* curNode, allNodes )
482  {
483  QVector< CartesianAxis* > sharedAxes;
484  CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes );
485  Q_ASSERT( sharedAxes.size() < 2 );
486  // TODO duplicated code make a method out of it
487  if ( sharedAxes.size() == 1 && xSharedPlanes.size() > 1 )
488  {
489  //xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
490  //std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
491  for ( int i = 0; i < xSharedPlanes.size() - 1; ++i )
492  {
493  LayoutGraphNode *tmpNode = planeNodeMapping[ xSharedPlanes[ i ] ];
494  Q_ASSERT( tmpNode );
495  LayoutGraphNode *tmpNode2 = planeNodeMapping[ xSharedPlanes[ i + 1 ] ];
496  Q_ASSERT( tmpNode2 );
497  tmpNode->bottomSuccesor = tmpNode2;
498  }
499 // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
500 // {
501 // LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ];
502 // Q_ASSERT( lastNode );
503 // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
504 // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
505 // Q_ASSERT( ownerNode );
506 // lastNode->bottomSuccesor = ownerNode;
507 // }
508  //merge AxisInformation, needs a two pass run
509  LayoutGraphNode axisInfoNode;
510  for ( int count = 0; count < 2; ++count )
511  {
512  for ( int i = 0; i < xSharedPlanes.size(); ++i )
513  {
514  mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ xSharedPlanes[ i ] ] );
515  }
516  }
517  }
518  sharedAxes.clear();
519  CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes );
520  Q_ASSERT( sharedAxes.size() < 2 );
521  if ( sharedAxes.size() == 1 && ySharedPlanes.size() > 1 )
522  {
523  //ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
524  //std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
525  for ( int i = 0; i < ySharedPlanes.size() - 1; ++i )
526  {
527  LayoutGraphNode *tmpNode = planeNodeMapping[ ySharedPlanes[ i ] ];
528  Q_ASSERT( tmpNode );
529  LayoutGraphNode *tmpNode2 = planeNodeMapping[ ySharedPlanes[ i + 1 ] ];
530  Q_ASSERT( tmpNode2 );
531  tmpNode->leftSuccesor = tmpNode2;
532  }
533 // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
534 // {
535 // LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ];
536 // Q_ASSERT( lastNode );
537 // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
538 // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
539 // Q_ASSERT( ownerNode );
540 // lastNode->bottomSuccesor = ownerNode;
541 // }
542  //merge AxisInformation, needs a two pass run
543  LayoutGraphNode axisInfoNode;
544  for ( int count = 0; count < 2; ++count )
545  {
546  for ( int i = 0; i < ySharedPlanes.size(); ++i )
547  {
548  mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ ySharedPlanes[ i ] ] );
549  }
550  }
551  }
552  sharedAxes.clear();
553  if ( curNode->diagramPlane->referenceCoordinatePlane() )
554  curNode->sharedSuccesor = planeNodeMapping[ curNode->diagramPlane->referenceCoordinatePlane() ];
555  }
556 
557  return allNodes;
558 }
559 
560 QHash<AbstractCoordinatePlane*, PlaneInfo> Chart::Private::buildPlaneLayoutInfos()
561 {
562  /* There are two ways in which planes can be caused to interact in
563  * where they are put layouting wise: The first is the reference plane. If
564  * such a reference plane is set, on a plane, it will use the same cell in the
565  * layout as that one. In addition to this, planes can share an axis. In that case
566  * they will be laid out in relation to each other as suggested by the position
567  * of the axis. If, for example Plane1 and Plane2 share an axis at position Left,
568  * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1
569  * also happens to be Plane2's referece plane, both planes are drawn over each
570  * other. The reference plane concept allows two planes to share the same space
571  * even if neither has any axis, and in case there are shared axis, it is used
572  * to decided, whether the planes should be painted on top of each other or
573  * laid out vertically or horizontally next to each other. */
574  QHash<CartesianAxis*, AxisInfo> axisInfos;
575  QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos;
576  Q_FOREACH(AbstractCoordinatePlane* plane, coordinatePlanes ) {
577  PlaneInfo p;
578  // first check if we share space with another plane
579  p.referencePlane = plane->referenceCoordinatePlane();
580  planeInfos.insert( plane, p );
581 
582  Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) {
583  AbstractCartesianDiagram* diagram =
584  qobject_cast<AbstractCartesianDiagram*> ( abstractDiagram );
585  if ( !diagram ) {
586  continue;
587  }
588 
589  Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
590  if ( !axisInfos.contains( axis ) ) {
591  /* If this is the first time we see this axis, add it, with the
592  * current plane. The first plane added to the chart that has
593  * the axis associated with it thus "owns" it, and decides about
594  * layout. */
595  AxisInfo i;
596  i.plane = plane;
597  axisInfos.insert( axis, i );
598  } else {
599  AxisInfo i = axisInfos[axis];
600  if ( i.plane == plane ) {
601  continue; // we don't want duplicates, only shared
602  }
603 
604  /* The user expects diagrams to be added on top, and to the right
605  * so that horizontally we need to move the new diagram, vertically
606  * the reference one. */
607  PlaneInfo pi = planeInfos[plane];
608  // plane-to-plane linking overrides linking via axes
609  if ( !pi.referencePlane ) {
610  // we're not the first plane to see this axis, mark us as a slave
611  pi.referencePlane = i.plane;
612  if ( axis->position() == CartesianAxis::Left ||
613  axis->position() == CartesianAxis::Right ) {
614  pi.horizontalOffset += 1;
615  }
616  planeInfos[plane] = pi;
617 
618  pi = planeInfos[i.plane];
619  if ( axis->position() == CartesianAxis::Top ||
620  axis->position() == CartesianAxis::Bottom ) {
621  pi.verticalOffset += 1;
622  }
623 
624  planeInfos[i.plane] = pi;
625  }
626  }
627  }
628  }
629  // Create a new grid layout for each plane that has no reference.
630  p = planeInfos[plane];
631  if ( p.referencePlane == 0 ) {
632  p.gridLayout = new QGridLayout();
633  p.gridLayout->setMargin( 0 );
634  planeInfos[plane] = p;
635  }
636  }
637  return planeInfos;
638 }
639 
640 void Chart::Private::slotLayoutPlanes()
641 {
642  /*TODO make sure this is really needed */
643  const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
644  : QBoxLayout::TopToBottom;
645  if ( planesLayout && dataAndLegendLayout )
646  dataAndLegendLayout->removeItem( planesLayout );
647 
648  const bool hadPlanesLayout = planesLayout != 0;
649  int left, top, right, bottom;
650  if ( hadPlanesLayout )
651  planesLayout->getContentsMargins(&left, &top, &right, &bottom);
652 
653  KDAB_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) {
654  plane->removeFromParentLayout();
655  }
656  //TODO they should get a correct parent, but for now it works
657  KDAB_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) {
658  if ( dynamic_cast< AutoSpacerLayoutItem* >( plane ) )
659  delete plane;
660  }
661 
662  planeLayoutItems.clear();
663  delete planesLayout;
664  //hint: The direction is configurable by the user now, as
665  // we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25)
666  planesLayout = new QBoxLayout( oldPlanesDirection );
667 
668  isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting
669 
670  if ( useNewLayoutSystem )
671  {
672  gridPlaneLayout = new QGridLayout;
673  planesLayout->addLayout( gridPlaneLayout );
674 
675  if (hadPlanesLayout)
676  planesLayout->setContentsMargins(left, top, right, bottom);
677  planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
678 
679  /* First go through all planes and all axes and figure out whether the planes
680  * need to coordinate. If they do, they share a grid layout, if not, each
681  * get their own. See buildPlaneLayoutInfos() for more details. */
682 
683  QVector< LayoutGraphNode* > vals = buildPlaneLayoutGraph();
684  //qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size();
686  //qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size();
687  int row = 0;
688  int col = 0;
689  QSet< CartesianAxis* > laidOutAxes;
690  for ( int i = 0; i < connectedComponents.size(); ++i )
691  {
692  LayoutGraphNode *curComponent = connectedComponents[ i ];
693  for ( LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor )
694  {
695  col = 0;
696  for ( LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor )
697  {
698  Q_ASSERT( curColComponent->diagramPlane->diagrams().size() == 1 );
699  Q_FOREACH( AbstractDiagram* diagram, curColComponent->diagramPlane->diagrams() )
700  {
701  const int planeRowOffset = 1;//curColComponent->topAxesLayout ? 1 : 0;
702  const int planeColOffset = 1;//curColComponent->leftAxesLayout ? 1 : 0;
703  //qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset;
704 
705  //qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset;
706  planeLayoutItems << curColComponent->diagramPlane;
707  AbstractCartesianDiagram *cartDiag = qobject_cast< AbstractCartesianDiagram* >( diagram );
708  if ( cartDiag )
709  {
710  gridPlaneLayout->addItem( curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
711  curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
712  QHBoxLayout *leftLayout = 0;
713  QHBoxLayout *rightLayout = 0;
714  QVBoxLayout *topLayout = 0;
715  QVBoxLayout *bottomLayout = 0;
716  if ( curComponent->sharedSuccesor )
717  {
718  gridPlaneLayout->addItem( curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
719  curColComponent->sharedSuccesor->diagramPlane->setParentLayout( gridPlaneLayout );
720  planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane;
721  }
722  Q_FOREACH( CartesianAxis* axis, cartDiag->axes() )
723  {
724  if ( axis->isAbscissa() )
725  {
726  if ( curColComponent->bottomSuccesor )
727  continue;
728  }
729  if ( laidOutAxes.contains( axis ) )
730  continue;
731  // if ( axis->diagram() != diagram )
732  // continue;
733  switch ( axis->position() )
734  {
735  case( CartesianAxis::Top ):
736  if ( !topLayout )
737  topLayout = new QVBoxLayout;
738  topLayout->addItem( axis );
739  axis->setParentLayout( topLayout );
740  break;
741  case( CartesianAxis::Bottom ):
742  if ( !bottomLayout )
743  bottomLayout = new QVBoxLayout;
744  bottomLayout->addItem( axis );
745  axis->setParentLayout( bottomLayout );
746  break;
747  case( CartesianAxis::Left ):
748  if ( !leftLayout )
749  leftLayout = new QHBoxLayout;
750  leftLayout->addItem( axis );
751  axis->setParentLayout( leftLayout );
752  break;
753  case( CartesianAxis::Right ):
754  if ( !rightLayout )
755  {
756  rightLayout = new QHBoxLayout;
757  }
758  rightLayout->addItem( axis );
759  axis->setParentLayout( rightLayout );
760  break;
761  }
762  planeLayoutItems << axis;
763  laidOutAxes.insert( axis );
764  }
765  if ( leftLayout )
766  gridPlaneLayout->addLayout( leftLayout, row + planeRowOffset, col, 2, 1,
767  Qt::AlignRight | Qt::AlignVCenter );
768  if ( rightLayout )
769  gridPlaneLayout->addLayout( rightLayout, row, col + planeColOffset + 2, 2, 1,
770  Qt::AlignLeft | Qt::AlignVCenter );
771  if ( topLayout )
772  gridPlaneLayout->addLayout( topLayout, row, col + planeColOffset, 1, 2,
773  Qt::AlignBottom | Qt::AlignHCenter );
774  if ( bottomLayout )
775  gridPlaneLayout->addLayout( bottomLayout, row + planeRowOffset + 2,
776  col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter );
777  }
778  else
779  {
780  gridPlaneLayout->addItem( curColComponent->diagramPlane, row, col, 4, 4 );
781  curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
782  }
783  col += planeColOffset + 2 + ( 1 );
784  }
785  }
786  int axisOffset = 2;//curRowComponent->topAxesLayout ? 1 : 0;
787  //axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0;
788  const int rowOffset = axisOffset + 2;
789  row += rowOffset;
790  }
791 
792  // if ( planesLayout->direction() == QBoxLayout::TopToBottom )
793  // ++row;
794  // else
795  // ++col;
796  }
797 
798  qDeleteAll( vals );
799  // re-add our grid(s) to the chart's layout
800  if ( dataAndLegendLayout ) {
801  dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
802  dataAndLegendLayout->setRowStretch( 1, 1000 );
803  dataAndLegendLayout->setColumnStretch( 1, 1000 );
804  }
805  slotResizePlanes();
806 #ifdef NEW_LAYOUT_DEBUG
807  for ( int i = 0; i < gridPlaneLayout->rowCount(); ++i )
808  {
809  for ( int j = 0; j < gridPlaneLayout->columnCount(); ++j )
810  {
811  if ( gridPlaneLayout->itemAtPosition( i, j ) )
812  qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition( i, j )->geometry();
813  else
814  qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present";
815  }
816  }
817  //qDebug() << Q_FUNC_INFO << "Relayout ended";
818 #endif
819  } else {
820  if ( hadPlanesLayout ) {
821  planesLayout->setContentsMargins( left, top, right, bottom );
822  }
823 
824  planesLayout->setMargin( 0 );
825  planesLayout->setSpacing( 0 );
826  planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
827 
828  /* First go through all planes and all axes and figure out whether the planes
829  * need to coordinate. If they do, they share a grid layout, if not, each
830  * gets their own. See buildPlaneLayoutInfos() for more details. */
831  QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
832  QHash<AbstractAxis*, AxisInfo> axisInfos;
833  KDAB_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) {
834  Q_ASSERT( planeInfos.contains(plane) );
835  PlaneInfo& pi = planeInfos[ plane ];
836  const int column = pi.horizontalOffset;
837  const int row = pi.verticalOffset;
838  //qDebug() << "processing plane at column" << column << "and row" << row;
839  QGridLayout *planeLayout = pi.gridLayout;
840 
841  if ( !planeLayout ) {
842  PlaneInfo& refPi = pi;
843  // if this plane is sharing an axis with another one, recursively check for the original plane and use
844  // the grid of that as planeLayout.
845  while ( !planeLayout && refPi.referencePlane ) {
846  refPi = planeInfos[refPi.referencePlane];
847  planeLayout = refPi.gridLayout;
848  }
849  Q_ASSERT_X( planeLayout,
850  "Chart::Private::slotLayoutPlanes()",
851  "Invalid reference plane. Please check that the reference plane has been added to the Chart." );
852  } else {
853  planesLayout->addLayout( planeLayout );
854  }
855 
856  /* Put the plane in the center of the layout. If this is our own, that's
857  * the middle of the layout, if we are sharing, it's a cell in the center
858  * column of the shared grid. */
859  planeLayoutItems << plane;
860  plane->setParentLayout( planeLayout );
861  planeLayout->addItem( plane, row, column, 1, 1, 0 );
862  //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
863  planeLayout->setRowStretch( row, 2 );
864  planeLayout->setColumnStretch( column, 2 );
865  KDAB_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() )
866  {
867  AbstractCartesianDiagram* diagram =
868  qobject_cast< AbstractCartesianDiagram* >( abstractDiagram );
869  if ( !diagram ) {
870  continue; // FIXME what about polar ?
871  }
872 
873  if ( pi.referencePlane != 0 )
874  {
875  pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout;
876  pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout;
877  pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout;
878  pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout;
879  }
880 
881  // collect all axes of a kind into sublayouts
882  if ( pi.topAxesLayout == 0 )
883  {
884  pi.topAxesLayout = new QVBoxLayout;
885  pi.topAxesLayout->setMargin( 0 );
886  pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
887  }
888  if ( pi.bottomAxesLayout == 0 )
889  {
890  pi.bottomAxesLayout = new QVBoxLayout;
891  pi.bottomAxesLayout->setMargin( 0 );
892  pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
893  }
894  if ( pi.leftAxesLayout == 0 )
895  {
896  pi.leftAxesLayout = new QHBoxLayout;
897  pi.leftAxesLayout->setMargin( 0 );
898  pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
899  }
900  if ( pi.rightAxesLayout == 0 )
901  {
902  pi.rightAxesLayout = new QHBoxLayout;
903  pi.rightAxesLayout->setMargin( 0 );
904  pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
905  }
906 
907  if ( pi.referencePlane != 0 )
908  {
909  planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout;
910  planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout;
911  planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout;
912  planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout;
913  }
914 
915  //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
916  KDAB_FOREACH( CartesianAxis* axis, diagram->axes() ) {
917  if ( axisInfos.contains( axis ) ) {
918  continue; // already laid out this one
919  }
920  Q_ASSERT ( axis );
921  axis->setCachedSizeDirty();
922  //qDebug() << "--------------- axis added to planeLayoutItems -----------------";
923  planeLayoutItems << axis;
924 
925  switch ( axis->position() ) {
926  case CartesianAxis::Top:
927  axis->setParentLayout( pi.topAxesLayout );
928  pi.topAxesLayout->addItem( axis );
929  break;
931  axis->setParentLayout( pi.bottomAxesLayout );
932  pi.bottomAxesLayout->addItem( axis );
933  break;
934  case CartesianAxis::Left:
935  axis->setParentLayout( pi.leftAxesLayout );
936  pi.leftAxesLayout->addItem( axis );
937  break;
939  axis->setParentLayout( pi.rightAxesLayout );
940  pi.rightAxesLayout->addItem( axis );
941  break;
942  default:
943  Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" );
944  break;
945  };
946  axisInfos.insert( axis, AxisInfo() );
947  }
948  /* Put each stack of axes-layouts in the cells surrounding the
949  * associated plane. We are laying out in the oder the planes
950  * were added, and the first one gets to lay out shared axes.
951  * Private axes go here as well, of course. */
952 
953  if ( !pi.topAxesLayout->parent() ) {
954  planeLayout->addLayout( pi.topAxesLayout, row - 1, column );
955  }
956  if ( !pi.bottomAxesLayout->parent() ) {
957  planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column );
958  }
959  if ( !pi.leftAxesLayout->parent() ) {
960  planeLayout->addLayout( pi.leftAxesLayout, row, column - 1 );
961  }
962  if ( !pi.rightAxesLayout->parent() ) {
963  planeLayout->addLayout( pi.rightAxesLayout,row, column + 1 );
964  }
965  }
966 
967  // use up to four auto-spacer items in the corners around the diagrams:
968  #define ADD_AUTO_SPACER_IF_NEEDED( \
969  spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \
970  { \
971  if ( hLayout || vLayout ) { \
972  AutoSpacerLayoutItem * spacer \
973  = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \
974  planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \
975  spacer->setParentLayout( planeLayout ); \
976  planeLayoutItems << spacer; \
977  } \
978  }
979 
980  if ( plane->isCornerSpacersEnabled() ) {
981  ADD_AUTO_SPACER_IF_NEEDED( row - 1, column - 1, false, pi.leftAxesLayout, false, pi.topAxesLayout )
982  ADD_AUTO_SPACER_IF_NEEDED( row + 1, column - 1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout )
983  ADD_AUTO_SPACER_IF_NEEDED( row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout )
984  ADD_AUTO_SPACER_IF_NEEDED( row + 1, column + 1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout )
985  }
986  }
987  // re-add our grid(s) to the chart's layout
988  if ( dataAndLegendLayout ) {
989  dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
990  dataAndLegendLayout->setRowStretch( 1, 1000 );
991  dataAndLegendLayout->setColumnStretch( 1, 1000 );
992  }
993 
994  slotResizePlanes();
995  }
996 }
997 
998 void Chart::Private::createLayouts()
999 {
1000  // The toplevel layout provides the left and right global margins
1001  layout = new QHBoxLayout( chart );
1002  layout->setMargin( 0 );
1003  layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
1004  layout->addSpacing( globalLeadingLeft );
1005  leftOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
1006 
1007  // The vLayout provides top and bottom global margins and lays
1008  // out headers, footers and the diagram area.
1009  vLayout = new QVBoxLayout();
1010  vLayout->setMargin( 0 );
1011  vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
1012 
1013  layout->addLayout( vLayout, 1000 );
1014  layout->addSpacing( globalLeadingRight );
1015  rightOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
1016 
1017  // 1. the spacing above the header area
1018  vLayout->addSpacing( globalLeadingTop );
1019  topOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
1020  // 2. the header area
1021  headerLayout = new QGridLayout();
1022  headerLayout->setMargin( 0 );
1023  vLayout->addLayout( headerLayout );
1024  // 3. the area containing coordinate planes, axes, and legends
1025  dataAndLegendLayout = new QGridLayout();
1026  dataAndLegendLayout->setMargin( 0 );
1027  dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
1028  vLayout->addLayout( dataAndLegendLayout, 1000 );
1029  // 4. the footer area
1030  footerLayout = new QGridLayout();
1031  footerLayout->setMargin( 0 );
1032  footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) );
1033  vLayout->addLayout( footerLayout );
1034 
1035  // 5. Prepare the header / footer layout cells:
1036  // Each of the 9 header cells (the 9 footer cells)
1037  // contain their own QVBoxLayout
1038  // since there can be more than one header (footer) per cell.
1039  for ( int row = 0; row < 3; ++row ) {
1040  for ( int column = 0; column < 3; ++ column ) {
1041  const Qt::Alignment align = s_gridAlignments[ row ][ column ];
1042  for ( int headOrFoot = 0; headOrFoot < 2; headOrFoot++ ) {
1043  QVBoxLayout* innerLayout = new QVBoxLayout();
1044  innerLayout->setMargin( 0 );
1045  innerLayout->setAlignment( align );
1046  innerHdFtLayouts[ headOrFoot ][ row ][ column ] = innerLayout;
1047 
1048  QGridLayout* outerLayout = headOrFoot == 0 ? headerLayout : footerLayout;
1049  outerLayout->addLayout( innerLayout, row, column, align );
1050  }
1051  }
1052  }
1053 
1054  // 6. the spacing below the footer area
1055  vLayout->addSpacing( globalLeadingBottom );
1056  bottomOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
1057 
1058  // the data+axes area
1059  dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
1060  dataAndLegendLayout->setRowStretch( 1, 1 );
1061  dataAndLegendLayout->setColumnStretch( 1, 1 );
1062 }
1063 
1064 void Chart::Private::slotResizePlanes()
1065 {
1066  if ( !dataAndLegendLayout ) {
1067  return;
1068  }
1069  if ( !overrideSize.isValid() ) {
1070  // activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize
1071  // is set. So don't let the layout grab the wrong size in that case.
1072  // When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ),
1073  // which also "activates" the layout in the sense that it distributes space internally.
1074  layout->activate();
1075  }
1076  // Adapt diagram drawing to the new size
1077  KDAB_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) {
1078  plane->layoutDiagrams();
1079  }
1080 }
1081 
1082 void Chart::Private::updateDirtyLayouts()
1083 {
1084  if ( isPlanesLayoutDirty ) {
1085  Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) {
1087  p->layoutPlanes();
1088  p->layoutDiagrams();
1089  }
1090  }
1091  if ( isPlanesLayoutDirty || isFloatingLegendsLayoutDirty ) {
1092  chart->reLayoutFloatingLegends();
1093  }
1094  isPlanesLayoutDirty = false;
1095  isFloatingLegendsLayoutDirty = false;
1096 }
1097 
1098 void Chart::Private::reapplyInternalLayouts()
1099 {
1100  QRect geo = layout->geometry();
1101 
1102  invalidateLayoutTree( layout );
1103  layout->setGeometry( geo );
1104  slotResizePlanes();
1105 }
1106 
1107 void Chart::Private::paintAll( QPainter* painter )
1108 {
1109  updateDirtyLayouts();
1110 
1111  QRect rect( QPoint( 0, 0 ), overrideSize.isValid() ? overrideSize : chart->size() );
1112 
1113  //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
1114 
1115  // Paint the background (if any)
1116  AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes );
1117  // Paint the frame (if any)
1118  AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes );
1119 
1120  chart->reLayoutFloatingLegends();
1121 
1122  KDAB_FOREACH( AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) {
1123  planeLayoutItem->paintAll( *painter );
1124  }
1125  KDAB_FOREACH( TextArea* textLayoutItem, textLayoutItems ) {
1126  textLayoutItem->paintAll( *painter );
1127  }
1128  KDAB_FOREACH( Legend *legend, legends ) {
1129  const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
1130  if ( !hidden ) {
1131  //qDebug() << "painting legend at " << legend->geometry();
1132  legend->paintIntoRect( *painter, legend->geometry() );
1133  }
1134  }
1135 }
1136 
1137 // ******** Chart interface implementation ***********
1138 
1139 #define d d_func()
1140 
1142  : QWidget ( parent )
1143  , _d( new Private( this ) )
1144 {
1145 #if defined KDAB_EVAL
1146  EvalDialog::checkEvalLicense( "KD Chart" );
1147 #endif
1148 
1149  FrameAttributes frameAttrs;
1150 // no frame per default...
1151 // frameAttrs.setVisible( true );
1152  frameAttrs.setPen( QPen( Qt::black ) );
1153  frameAttrs.setPadding( 1 );
1154  setFrameAttributes( frameAttrs );
1155 
1157 
1158  d->createLayouts();
1159 }
1160 
1162 {
1163  delete d;
1164 }
1165 
1167 {
1168  d->frameAttributes = a;
1169 }
1170 
1172 {
1173  return d->frameAttributes;
1174 }
1175 
1177 {
1178  d->backgroundAttributes = a;
1179 }
1180 
1182 {
1183  return d->backgroundAttributes;
1184 }
1185 
1186 //TODO KDChart 3.0; change QLayout into QBoxLayout::Direction
1187 void Chart::setCoordinatePlaneLayout( QLayout * layout )
1188 {
1189  if (layout == d->planesLayout)
1190  return;
1191  if (d->planesLayout) {
1192  // detach all QLayoutItem's the previous planesLayout has cause
1193  // otherwise deleting the planesLayout would delete them too.
1194  for(int i = d->planesLayout->count() - 1; i >= 0; --i) {
1195  d->planesLayout->takeAt(i);
1196  }
1197  delete d->planesLayout;
1198  }
1199  d->planesLayout = qobject_cast<QBoxLayout*>( layout );
1200  d->slotLayoutPlanes();
1201 }
1202 
1204 {
1205  return d->planesLayout;
1206 }
1207 
1209 {
1210  if ( d->coordinatePlanes.isEmpty() ) {
1211  qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
1212  return 0;
1213  } else {
1214  return d->coordinatePlanes.first();
1215  }
1216 }
1217 
1219 {
1220  return d->coordinatePlanes;
1221 }
1222 
1224 {
1225  // Append
1226  insertCoordinatePlane( d->coordinatePlanes.count(), plane );
1227 }
1228 
1230 {
1231  if ( index < 0 || index > d->coordinatePlanes.count() ) {
1232  return;
1233  }
1234 
1235  connect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
1236  d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
1237  connect( plane, SIGNAL( needUpdate() ), this, SLOT( update() ) );
1238  connect( plane, SIGNAL( needRelayout() ), d, SLOT( slotResizePlanes() ) ) ;
1239  connect( plane, SIGNAL( needLayoutPlanes() ), d, SLOT( slotLayoutPlanes() ) ) ;
1240  connect( plane, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) );
1241  d->coordinatePlanes.insert( index, plane );
1242  plane->setParent( this );
1243  d->slotLayoutPlanes();
1244 }
1245 
1247  AbstractCoordinatePlane* oldPlane_ )
1248 {
1249  if ( plane && oldPlane_ != plane ) {
1250  AbstractCoordinatePlane* oldPlane = oldPlane_;
1251  if ( d->coordinatePlanes.count() ) {
1252  if ( ! oldPlane ) {
1253  oldPlane = d->coordinatePlanes.first();
1254  if ( oldPlane == plane )
1255  return;
1256  }
1257  takeCoordinatePlane( oldPlane );
1258  }
1259  delete oldPlane;
1260  addCoordinatePlane( plane );
1261  }
1262 }
1263 
1265 {
1266  const int idx = d->coordinatePlanes.indexOf( plane );
1267  if ( idx != -1 ) {
1268  d->coordinatePlanes.takeAt( idx );
1269  disconnect( plane, 0, d, 0 );
1270  disconnect( plane, 0, this, 0 );
1271  plane->removeFromParentLayout();
1272  plane->setParent( 0 );
1273  d->mouseClickedPlanes.removeAll(plane);
1274  }
1275  d->slotLayoutPlanes();
1276  // Need to emit the signal: In case somebody has connected the signal
1277  // to her own slot for e.g. calling update() on a widget containing the chart.
1278  emit propertiesChanged();
1279 }
1280 
1281 void Chart::setGlobalLeading( int left, int top, int right, int bottom )
1282 {
1283  setGlobalLeadingLeft( left );
1284  setGlobalLeadingTop( top );
1285  setGlobalLeadingRight( right );
1286  setGlobalLeadingBottom( bottom );
1287 }
1288 
1289 void Chart::setGlobalLeadingLeft( int leading )
1290 {
1291  d->globalLeadingLeft = leading;
1292  d->leftOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
1293  d->reapplyInternalLayouts();
1294 }
1295 
1296 int Chart::globalLeadingLeft() const
1297 {
1298  return d->globalLeadingLeft;
1299 }
1300 
1301 void Chart::setGlobalLeadingTop( int leading )
1302 {
1303  d->globalLeadingTop = leading;
1304  d->topOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
1305  d->reapplyInternalLayouts();
1306 }
1307 
1308 int Chart::globalLeadingTop() const
1309 {
1310  return d->globalLeadingTop;
1311 }
1312 
1313 void Chart::setGlobalLeadingRight( int leading )
1314 {
1315  d->globalLeadingRight = leading;
1316  d->rightOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
1317  d->reapplyInternalLayouts();
1318 }
1319 
1320 int Chart::globalLeadingRight() const
1321 {
1322  return d->globalLeadingRight;
1323 }
1324 
1326 {
1327  d->globalLeadingBottom = leading;
1328  d->bottomOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
1329  d->reapplyInternalLayouts();
1330 }
1331 
1332 int Chart::globalLeadingBottom() const
1333 {
1334  return d->globalLeadingBottom;
1335 }
1336 
1337 void Chart::paint( QPainter* painter, const QRect& target )
1338 {
1339  if ( target.isEmpty() || !painter ) {
1340  return;
1341  }
1342 
1344  GlobalMeasureScaling::setPaintDevice( painter->device() );
1345 
1346  // Output on a widget
1347  if ( dynamic_cast< QWidget* >( painter->device() ) != 0 ) {
1348  GlobalMeasureScaling::setFactors( qreal( target.width() ) / qreal( geometry().size().width() ),
1349  qreal( target.height() ) / qreal( geometry().size().height() ) );
1350  } else {
1351  // Output onto a QPixmap
1352  PrintingParameters::setScaleFactor( qreal( painter->device()->logicalDpiX() ) / qreal( logicalDpiX() ) );
1353 
1354  const qreal resX = qreal( logicalDpiX() ) / qreal( painter->device()->logicalDpiX() );
1355  const qreal resY = qreal( logicalDpiY() ) / qreal( painter->device()->logicalDpiY() );
1356 
1357  GlobalMeasureScaling::setFactors( qreal( target.width() ) / qreal( geometry().size().width() ) * resX,
1358  qreal( target.height() ) / qreal( geometry().size().height() ) * resY );
1359  }
1360 
1361  const QPoint translation = target.topLeft();
1362  painter->translate( translation );
1363 
1364  // the following layout logic has the disadvantage that repeatedly calling this method can
1365  // cause a relayout every time, but since this method's main use seems to be printing, the
1366  // gratuitous relayouts shouldn't be much of a performance problem.
1367  const bool differentSize = target.size() != size();
1368  QRect oldGeometry;
1369  if ( differentSize ) {
1370  oldGeometry = geometry();
1371  d->isPlanesLayoutDirty = true;
1372  d->isFloatingLegendsLayoutDirty = true;
1373  invalidateLayoutTree( d->dataAndLegendLayout );
1374  d->dataAndLegendLayout->setGeometry( QRect( QPoint(), target.size() ) );
1375  }
1376 
1377  d->overrideSize = target.size();
1378  d->paintAll( painter );
1379  d->overrideSize = QSize();
1380 
1381  if ( differentSize ) {
1382  invalidateLayoutTree( d->dataAndLegendLayout );
1383  d->dataAndLegendLayout->setGeometry( oldGeometry );
1384  d->isPlanesLayoutDirty = true;
1385  d->isFloatingLegendsLayoutDirty = true;
1386  }
1387 
1388  // for debugging
1389  // painter->setPen( QPen( Qt::blue, 8 ) );
1390  // painter->drawRect( target );
1391 
1392  painter->translate( -translation.x(), -translation.y() );
1393 
1397 }
1398 
1399 void Chart::resizeEvent ( QResizeEvent* event )
1400 {
1401  d->isPlanesLayoutDirty = true;
1402  d->isFloatingLegendsLayoutDirty = true;
1403  QWidget::resizeEvent( event );
1404 }
1405 
1407 {
1408  KDAB_FOREACH( Legend *legend, d->legends ) {
1409  const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
1410  if ( legend->position().isFloating() && !hidden ) {
1411  // resize the legend
1412  const QSize legendSize( legend->sizeHint() );
1413  legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) );
1414  // find the legends corner point (reference point plus any paddings)
1415  const RelativePosition relPos( legend->floatingPosition() );
1416  QPointF pt( relPos.calculatedPoint( size() ) );
1417  //qDebug() << pt;
1418  // calculate the legend's top left point
1419  const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft;
1420  if ( (relPos.alignment() & alignTopLeft) != alignTopLeft ) {
1421  if ( relPos.alignment() & Qt::AlignRight )
1422  pt.rx() -= legendSize.width();
1423  else if ( relPos.alignment() & Qt::AlignHCenter )
1424  pt.rx() -= 0.5 * legendSize.width();
1425 
1426  if ( relPos.alignment() & Qt::AlignBottom )
1427  pt.ry() -= legendSize.height();
1428  else if ( relPos.alignment() & Qt::AlignVCenter )
1429  pt.ry() -= 0.5 * legendSize.height();
1430  }
1431  //qDebug() << pt << endl;
1432  legend->move( static_cast<int>(pt.x()), static_cast<int>(pt.y()) );
1433  }
1434  }
1435 }
1436 
1437 
1438 void Chart::paintEvent( QPaintEvent* )
1439 {
1440  QPainter painter( this );
1441  d->paintAll( &painter );
1442  emit finishedDrawing();
1443 }
1444 
1446 {
1447  Q_ASSERT( hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer );
1448  int row;
1449  int column;
1450  getRowAndColumnForPosition( hf->position().value(), &row, &column );
1451  if ( row == -1 ) {
1452  qWarning( "Unknown header/footer position" );
1453  return;
1454  }
1455 
1456  d->headerFooters.append( hf );
1457  d->textLayoutItems.append( hf );
1458  connect( hf, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
1459  d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
1460  connect( hf, SIGNAL( positionChanged( HeaderFooter* ) ),
1461  d, SLOT( slotHeaderFooterPositionChanged( HeaderFooter* ) ) );
1462 
1463  // set the text attributes (why?)
1464 
1465  TextAttributes textAttrs( hf->textAttributes() );
1466  Measure measure( textAttrs.fontSize() );
1468  measure.setValue( 20 );
1469  textAttrs.setFontSize( measure );
1470  hf->setTextAttributes( textAttrs );
1471 
1472  // add it to the appropriate layout
1473 
1474  int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1;
1475  QVBoxLayout* headerFooterLayout = d->innerHdFtLayouts[ innerLayoutIdx ][ row ][ column ];
1476 
1477  hf->setParentLayout( headerFooterLayout );
1478  hf->setAlignment( s_gridAlignments[ row ][ column ] );
1479  headerFooterLayout->addItem( hf );
1480 
1481  d->slotResizePlanes();
1482 }
1483 
1485  HeaderFooter* oldHeaderFooter_ )
1486 {
1487  if ( headerFooter && oldHeaderFooter_ != headerFooter ) {
1488  HeaderFooter* oldHeaderFooter = oldHeaderFooter_;
1489  if ( d->headerFooters.count() ) {
1490  if ( ! oldHeaderFooter ) {
1491  oldHeaderFooter = d->headerFooters.first();
1492  if ( oldHeaderFooter == headerFooter )
1493  return;
1494  }
1495  takeHeaderFooter( oldHeaderFooter );
1496  }
1497  delete oldHeaderFooter;
1498  addHeaderFooter( headerFooter );
1499  }
1500 }
1501 
1503 {
1504  const int idx = d->headerFooters.indexOf( headerFooter );
1505  if ( idx == -1 ) {
1506  return;
1507  }
1508  disconnect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
1509  d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
1510 
1511  d->headerFooters.takeAt( idx );
1512  headerFooter->removeFromParentLayout();
1513  headerFooter->setParentLayout( 0 );
1514  d->textLayoutItems.remove( d->textLayoutItems.indexOf( headerFooter ) );
1515 
1516  d->slotResizePlanes();
1517 }
1518 
1519 void Chart::Private::slotHeaderFooterPositionChanged( HeaderFooter* hf )
1520 {
1521  chart->takeHeaderFooter( hf );
1522  chart->addHeaderFooter( hf );
1523 }
1524 
1526 {
1527  if ( d->headerFooters.isEmpty() ) {
1528  return 0;
1529  } else {
1530  return d->headerFooters.first();
1531  }
1532 }
1533 
1535 {
1536  return d->headerFooters;
1537 }
1538 
1539 void Chart::Private::slotLegendPositionChanged( AbstractAreaWidget* aw )
1540 {
1541  Legend* legend = qobject_cast< Legend* >( aw );
1542  Q_ASSERT( legend );
1543  chart->takeLegend( legend );
1544  chart->addLegendInternal( legend, false );
1545 }
1546 
1548 {
1549  legend->show();
1550  addLegendInternal( legend, true );
1551  emit propertiesChanged();
1552 }
1553 
1554 void Chart::addLegendInternal( Legend* legend, bool setMeasures )
1555 {
1556  if ( !legend ) {
1557  return;
1558  }
1559 
1560  KDChartEnums::PositionValue pos = legend->position().value();
1561  if ( pos == KDChartEnums::PositionCenter ) {
1562  qWarning( "Not showing legend because PositionCenter is not supported for legends." );
1563  }
1564 
1565  int row;
1566  int column;
1567  getRowAndColumnForPosition( pos, &row, &column );
1568  if ( row < 0 && pos != KDChartEnums::PositionFloating ) {
1569  qWarning( "Not showing legend because of unknown legend position." );
1570  return;
1571  }
1572 
1573  d->legends.append( legend );
1574  legend->setParent( this );
1575 
1576  // set text attributes (why?)
1577 
1578  if ( setMeasures ) {
1579  TextAttributes textAttrs( legend->textAttributes() );
1580  Measure measure( textAttrs.fontSize() );
1582  measure.setValue( 20 );
1583  textAttrs.setFontSize( measure );
1584  legend->setTextAttributes( textAttrs );
1585 
1586  textAttrs = legend->titleTextAttributes();
1587  measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
1588  measure.setValue( 24 );
1589  textAttrs.setFontSize( measure );
1590 
1591  legend->setTitleTextAttributes( textAttrs );
1592  legend->setReferenceArea( this );
1593  }
1594 
1595  // add it to the appropriate layout
1596 
1597  if ( pos != KDChartEnums::PositionFloating ) {
1598  legend->needSizeHint();
1599 
1600  // in each edge and corner of the outer layout, there's a grid for the different alignments that we create
1601  // on demand. we don't remove it when empty.
1602 
1603  QLayoutItem* edgeItem = d->dataAndLegendLayout->itemAtPosition( row, column );
1604  QGridLayout* alignmentsLayout = dynamic_cast< QGridLayout* >( edgeItem );
1605  Q_ASSERT( !edgeItem || alignmentsLayout ); // if it exists, it must be a QGridLayout
1606  if ( !alignmentsLayout ) {
1607  alignmentsLayout = new QGridLayout;
1608  d->dataAndLegendLayout->addLayout( alignmentsLayout, row, column );
1609  alignmentsLayout->setMargin( 0 );
1610  }
1611 
1612  // in case there are several legends in the same edge or corner with the same alignment, they are stacked
1613  // vertically using a QVBoxLayout. it is created on demand as above.
1614 
1615  row = 1;
1616  column = 1;
1617  for ( int i = 0; i < 3; i++ ) {
1618  for ( int j = 0; j < 3; j++ ) {
1619  Qt::Alignment align = s_gridAlignments[ i ][ j ];
1620  if ( align == legend->alignment() ) {
1621  row = i;
1622  column = j;
1623  break;
1624  }
1625  }
1626  }
1627 
1628  QLayoutItem* alignmentItem = alignmentsLayout->itemAtPosition( row, column );
1629  QVBoxLayout* sameAlignmentLayout = dynamic_cast< QVBoxLayout* >( alignmentItem );
1630  Q_ASSERT( !alignmentItem || sameAlignmentLayout ); // if it exists, it must be a QVBoxLayout
1631  if ( !sameAlignmentLayout ) {
1632  sameAlignmentLayout = new QVBoxLayout;
1633  alignmentsLayout->addLayout( sameAlignmentLayout, row, column );
1634  sameAlignmentLayout->setMargin( 0 );
1635  }
1636 
1637  sameAlignmentLayout->addItem( new MyWidgetItem( legend, legend->alignment() ) );
1638  }
1639 
1640  connect( legend, SIGNAL( destroyedLegend( Legend* ) ),
1641  d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
1642  connect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
1643  d, SLOT( slotLegendPositionChanged( AbstractAreaWidget* ) ) );
1644  connect( legend, SIGNAL( propertiesChanged() ), this, SIGNAL( propertiesChanged() ) );
1645 
1646  d->slotResizePlanes();
1647 }
1648 
1649 void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ )
1650 {
1651  if ( legend && oldLegend_ != legend ) {
1652  Legend* oldLegend = oldLegend_;
1653  if ( d->legends.count() ) {
1654  if ( ! oldLegend ) {
1655  oldLegend = d->legends.first();
1656  if ( oldLegend == legend )
1657  return;
1658  }
1659  takeLegend( oldLegend );
1660  }
1661  delete oldLegend;
1662  addLegend( legend );
1663  }
1664 }
1665 
1666 void Chart::takeLegend( Legend* legend )
1667 {
1668  const int idx = d->legends.indexOf( legend );
1669  if ( idx == -1 ) {
1670  return;
1671  }
1672 
1673  d->legends.takeAt( idx );
1674  disconnect( legend, 0, d, 0 );
1675  disconnect( legend, 0, this, 0 );
1676  // the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout)
1677  legend->setParent( 0 );
1678 
1679  d->slotResizePlanes();
1680  emit propertiesChanged();
1681 }
1682 
1684 {
1685  return d->legends.isEmpty() ? 0 : d->legends.first();
1686 }
1687 
1689 {
1690  return d->legends;
1691 }
1692 
1693 void Chart::mousePressEvent( QMouseEvent* event )
1694 {
1695  const QPoint pos = mapFromGlobal( event->globalPos() );
1696 
1697  KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1698  if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1699  QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
1700  event->button(), event->buttons(), event->modifiers() );
1701  plane->mousePressEvent( &ev );
1702  d->mouseClickedPlanes.append( plane );
1703  }
1704  }
1705 }
1706 
1708 {
1709  const QPoint pos = mapFromGlobal( event->globalPos() );
1710 
1711  KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1712  if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1713  QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
1714  event->button(), event->buttons(), event->modifiers() );
1715  plane->mouseDoubleClickEvent( &ev );
1716  }
1717  }
1718 }
1719 
1720 void Chart::mouseMoveEvent( QMouseEvent* event )
1721 {
1722  QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
1723 
1724  KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1725  if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1726  eventReceivers.insert( plane );
1727  }
1728  }
1729 
1730  const QPoint pos = mapFromGlobal( event->globalPos() );
1731 
1732  KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) {
1733  QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(),
1734  event->button(), event->buttons(), event->modifiers() );
1735  plane->mouseMoveEvent( &ev );
1736  }
1737 }
1738 
1739 void Chart::mouseReleaseEvent( QMouseEvent* event )
1740 {
1741  QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
1742 
1743  KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1744  if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1745  eventReceivers.insert( plane );
1746  }
1747  }
1748 
1749  const QPoint pos = mapFromGlobal( event->globalPos() );
1750 
1751  KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) {
1752  QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(),
1753  event->button(), event->buttons(), event->modifiers() );
1754  plane->mouseReleaseEvent( &ev );
1755  }
1756 
1757  d->mouseClickedPlanes.clear();
1758 }
1759 
1760 bool Chart::event( QEvent* event )
1761 {
1762  if ( event->type() == QEvent::ToolTip ) {
1763  const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
1764  for (int stage = 0; stage < 2; ++stage) {
1765  KDAB_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes ) {
1766  KDAB_FOREACH( const AbstractDiagram* diagram, plane->diagrams() ) {
1767 
1768  QModelIndex index;
1769  if (stage == 0) {
1770  // First search at the exact position
1771  index = diagram->indexAt(helpEvent->pos());
1772  }
1773  else {
1774  // Second, search in a larger area, which is easier to hit on screens with higher DPI.
1775  const QModelIndexList indexes = diagram->indexesIn(QRect( helpEvent->pos() - QPoint(15,15), QSize(30,30)));
1776  index = indexes.isEmpty() ? QModelIndex() : indexes.front();
1777  }
1778 
1779  const QVariant toolTip = index.data( Qt::ToolTipRole );
1780  if ( toolTip.isValid() ) {
1781  const QPoint pos = mapFromGlobal( helpEvent->pos() );
1782  const QRect rect( pos - QPoint( 1, 1 ), QSize( 3, 3 ) );
1783  QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect );
1784  return true;
1785  }
1786  }
1787  }
1788  }
1789  }
1790  return QWidget::event( event );
1791 }
1792 
1793 bool Chart::useNewLayoutSystem() const
1794 {
1795  return d_func()->useNewLayoutSystem;
1796 }
1798 {
1799  if ( d_func()->useNewLayoutSystem != value )
1800  d_func()->useNewLayoutSystem = value;
1801 }
Position position() const
Returns the position of a non-floating legend.
static void paintFrameAttributes(QPainter &painter, const QRect &rectangle, const KDChart::FrameAttributes &attributes)
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
KDChartEnums::PositionValue value() const
Returns an integer value corresponding to this Position.
int globalLeadingBottom() const
The padding between the start of the widget and the start of the area that is used for drawing at the...
virtual void paintIntoRect(QPainter &painter, const QRect &rect)
Draws the background and frame, then calls paint().
static CoordinatePlaneList findSharingAxisDiagrams(AbstractCoordinatePlane *plane, const CoordinatePlaneList &list, Chart::Private::AxisType type, QVector< CartesianAxis * > *sharedAxes)
bool event(QEvent *event) override
reimp
void mouseReleaseEvent(QMouseEvent *event) override
reimp
void replaceCoordinatePlane(AbstractCoordinatePlane *plane, AbstractCoordinatePlane *oldPlane=0)
Replaces the old coordinate plane, or appends the plane, it there is none yet.
static void setScaleFactor(const qreal scaleFactor)
static void paintBackgroundAttributes(QPainter &painter, const QRect &rectangle, const KDChart::BackgroundAttributes &attributes)
void setParent(Chart *parent)
Called internally by KDChart::Chart.
void reLayoutFloatingLegends()
void setReferenceArea(const QWidget *area)
Specifies the reference area for font size of title text, and for font size of the item texts...
QRect geometry() const override
pure virtual in QLayoutItem
static void invalidateLayoutTree(QLayoutItem *item)
bool isFloating() const
A chart with one or more diagrams.
Definition: KDChartChart.h:98
virtual void layoutDiagrams()=0
Distribute the available space among the diagrams and axes.
void setTextAttributes(const TextAttributes &a)
Definition of global enums.
Chart(QWidget *parent=0)
void checkExistingAxes(LayoutGraphNode *node)
TextAttributes titleTextAttributes() const
LegendList legends()
The list of all legends associated with the chart.
void layoutPlanes()
Calling layoutPlanes() on the plane triggers the global KDChart::Chart::slotLayoutPlanes() ...
A set of attributes for frames around items.
TextAttributes textAttributes() const
Returns the text attributes to be used for this item.
HeaderFooter * headerFooter()
The first header or footer of the chart.
static void setPaintDevice(QPaintDevice *paintDevice)
Set the paint device to use for calculating font metrics.
void needSizeHint() override
Call this to trigger an conditional re-building of the widget&#39;s internals.
virtual void mouseMoveEvent(QMouseEvent *event)
A text area in the chart with a background, a frame, etc.
void mouseMoveEvent(QMouseEvent *event) override
reimp
static QVector< LayoutGraphNode * > getPrioritySortedConnectedComponents(QVector< LayoutGraphNode * > &nodeList)
void takeHeaderFooter(HeaderFooter *headerFooter)
Removes the header (or footer, resp.) from the chart, without deleting it.
static QPaintDevice * paintDevice()
Return the paint device to use for calculating font metrics.
QLayout * coordinatePlaneLayout()
CoordinatePlaneList coordinatePlanes()
The list of coordinate planes.
void setFrameAttributes(const FrameAttributes &a)
Specify the frame attributes to be used, by default is it a thin black line.
void setGlobalLeadingRight(int leading)
Set the padding between the start of the widget and the start of the area that is used for drawing on...
AbstractDiagram defines the interface for diagram classes.
void takeCoordinatePlane(AbstractCoordinatePlane *plane)
Removes the coordinate plane from the chart, without deleting it.
void paint(QPainter *painter, const QRect &target)
Paints all the contents of the chart.
void setUseNewLayoutSystem(bool value)
static void getRowAndColumnForPosition(KDChartEnums::PositionValue pos, int *row, int *column)
void insertCoordinatePlane(int index, AbstractCoordinatePlane *plane)
Inserts a coordinate plane to the chart at index index.
void setGlobalLeadingTop(int leading)
Set the padding between the start of the widget and the start of the area that is used for drawing at...
An area in the chart with a background, a frame, etc.
QSize sizeHint() const override
The class for cartesian axes.
void setTitleTextAttributes(const TextAttributes &a)
void paintAll(QPainter &painter) override
Call paintAll, if you want the background and the frame to be drawn before the normal paint() is invo...
void replaceHeaderFooter(HeaderFooter *headerFooter, HeaderFooter *oldHeaderFooter=0)
Replaces the old header (or footer, resp.), or appends the new header or footer, it there is none yet...
#define ADD_AUTO_SPACER_IF_NEEDED(spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout)
virtual void paintAll(QPainter &painter)
Default impl: just call paint.
Class only listed here to document inheritance of some KDChart classes.
static GlobalMeasureScaling * instance()
VisitorState
void setReferenceCoordinatePlane(AbstractCoordinatePlane *plane)
Set another coordinate plane to be used as the reference plane for this one.
void setFontSize(const Measure &measure)
Set the size of the font used for rendering text.
void addLegend(Legend *legend)
Add the given legend to the chart.
BackgroundAttributes backgroundAttributes() const
Base class for all layout items of KD Chart.
Legend defines the interface for the legend drawing class.
Definition: KDChartLegend.h:55
virtual void mouseReleaseEvent(QMouseEvent *event)
static void mergeNodeAxisInformation(LayoutGraphNode *lhs, LayoutGraphNode *rhs)
virtual KDChart::CartesianAxisList axes() const
void setGlobalLeadingBottom(int leading)
Set the padding between the start of the widget and the start of the area that is used for drawing on...
void setTextAttributes(const TextAttributes &a)
Use this to specify the text attributes to be used for this item.
virtual bool isAbscissa() const
static void resetFactors()
Restore factors to the values before the previous call to setFactors.
void takeLegend(Legend *legend)
Removes the legend from the chart, without deleting it.
static const Qt::Alignment s_gridAlignments[3][3]
FrameAttributes frameAttributes() const
void addHeaderFooter(HeaderFooter *headerFooter)
Adds a header or a footer to the chart.
void finishedDrawing()
~Chart() override
void setCoordinatePlaneLayout(QLayout *layout)
Set the coordinate plane layout that should be used as model for the internal used layout...
Base class for diagrams based on a cartesian coordianate system.
PositionValue
Numerical values of the static KDChart::Position instances, for using a Position::value() with a swit...
Definition: KDChartEnums.h:192
AbstractCoordinatePlane * referenceCoordinatePlane() const
There are two ways, in which planes can be caused to interact, in where they are put layouting wise: ...
virtual const Position position() const
void mouseDoubleClickEvent(QMouseEvent *event) override
reimp
bool useNewLayoutSystem() const
useNewLayoutSystem Be very careful activating the new layout system, its still experimental and works...
TextAttributes textAttributes() const
int globalLeadingLeft() const
The padding between the start of the widget and the start of the area that is used for drawing on the...
const RelativePosition floatingPosition() const
Returns the position of a floating legend.
void setRelativeMode(const QObject *area, KDChartEnums::MeasureOrientation orientation)
The reference area must either be derived from AbstractArea or from QWidget, so it can also be derive...
Measure is used to specify relative and absolute sizes in KDChart, e.g.
void addCoordinatePlane(AbstractCoordinatePlane *plane)
Adds a coordinate plane to the chart.
void resizeEvent(QResizeEvent *event) override
Adjusts the internal layout when the chart is resized.
void replaceLegend(Legend *legend, Legend *oldLegend=0)
Replaces the old legend, or appends the new legend, it there is none yet.
#define d
HeaderFooterList headerFooters()
The list of headers and footers associated with the chart.
Defines relative position information: reference area, position in this area (reference position)...
virtual void mouseDoubleClickEvent(QMouseEvent *event)
void setBackgroundAttributes(const BackgroundAttributes &a)
Specify the background attributes to be used, by default there is no background.
void setParentLayout(QLayout *lay)
void setGlobalLeadingLeft(int leading)
Set the padding between the start of the widget and the start of the area that is used for drawing on...
Qt::Alignment alignment() const
Returns the alignment of a non-floating legend.
int globalLeadingTop() const
The padding between the start of the widget and the start of the area that is used for drawing at the...
QModelIndexList indexesIn(const QRect &rect) const
Legend * legend()
The first legend of the chart or 0 if there was none added to the chart.
Class only listed here to document inheritance of some KDChart classes.
void propertiesChanged()
Emitted upon change of a property of the Chart or any of its components.
static void setFactors(qreal factorX, qreal factorY)
Set new factors to be used by all Measure objects from now on.
void setGlobalLeading(int left, int top, int right, int bottom)
Set the padding between the margin of the widget and the area that the contents are drawn into...
int globalLeadingRight() const
The padding between the start of the widget and the start of the area that is used for drawing on the...
void setGridNeedsRecalculate()
Used by the chart to clear the cached grid data.
QModelIndex indexAt(const QPoint &point) const override
[reimplemented]
AbstractCoordinatePlane * coordinatePlane()
Each chart must have at least one coordinate plane.
A set of text attributes.
QList< AbstractCoordinatePlane * > CoordinatePlaneList
Definition: KDChartChart.h:74
Set of attributes usable for background pixmaps.
void mousePressEvent(QMouseEvent *event) override
reimp
void paintEvent(QPaintEvent *event) override
Draws the background and frame, then calls paint().
virtual void mousePressEvent(QMouseEvent *event)

Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/

https://www.kdab.com/development-resources/qt-tools/kd-chart/