KChart

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

KDE's Doxygen guidelines are available online.