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

© 2001 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
https://www.kdab.com/development-resources/qt-tools/kd-chart/
Generated by doxygen 1.9.1