KChart

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

KDE's Doxygen guidelines are available online.