KD Chart API Documentation 3.1
Loading...
Searching...
No Matches
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"
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().
54static bool isZeroArea(const QRect &r)
55{
56 return !r.width() || !r.height();
57}
58
59static 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
66static 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
101static void dumpLayoutTree(QLayout *l)
102{
103 int counter = 0;
104 dumpLayoutTreeRecurse(l, &counter, 0);
105}
106#endif
107
112
113static 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
159using 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...
163class MyWidgetItem : public QWidgetItem
164{
165public:
166 explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = {})
167 : QWidgetItem(w)
168 {
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
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)
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.
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
260void Chart::Private::slotUnregisterDestroyedLegend(Legend *l)
261{
262 chart->takeLegend(l);
263}
264
265void Chart::Private::slotUnregisterDestroyedHeaderFooter(HeaderFooter *hf)
266{
267 chart->takeHeaderFooter(hf);
268}
269
270void 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
281Chart::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
310Chart::Private::~Private()
311{
312}
313
319struct ConnectedComponentsComparator
320{
321 bool operator()(const LayoutGraphNode *lhs, const LayoutGraphNode *rhs) const
322 {
323 return lhs->priority < rhs->priority;
324 }
325};
326
328{
329 QVector<LayoutGraphNode *> connectedComponents;
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) {
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
361struct PriorityComparator
362{
363public:
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
378};
379
380void 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;
392 node->bottomAxesLayout = true;
393 break;
394 case (CartesianAxis::Left):
395 node->leftAxesLayout = true;
396 break;
398 node->rightAxesLayout = true;
399 break;
400 }
401 }
402 }
403 }
404}
405
406static 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
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
472QVector<LayoutGraphNode *> Chart::Private::buildPlaneLayoutGraph()
473{
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
557QHash<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. */
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
637void Chart::Private::slotLayoutPlanes()
638{
639 /*TODO make sure this is really needed */
640 const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
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();
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;
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;
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,
756 if (rightLayout)
757 gridPlaneLayout->addLayout(rightLayout, row, col + planeColOffset + 2, 2, 1,
759 if (topLayout)
760 gridPlaneLayout->addLayout(topLayout, row, col + planeColOffset, 1, 2,
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();
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()) {
906 axis->setParentLayout(pi.topAxesLayout);
907 pi.topAxesLayout->addItem(axis);
908 break;
910 axis->setParentLayout(pi.bottomAxesLayout);
911 pi.bottomAxesLayout->addItem(axis);
912 break;
914 axis->setParentLayout(pi.leftAxesLayout);
915 pi.leftAxesLayout->addItem(axis);
916 break;
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
976void 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
1042void 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
1060void 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
1076void Chart::Private::reapplyInternalLayouts()
1077{
1078 QRect geo = layout->geometry();
1079
1080 invalidateLayoutTree(layout);
1081 layout->setGeometry(geo);
1082 slotResizePlanes();
1083}
1084
1085void 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
1120 : QWidget(parent)
1121 , _d(new Private(this))
1122{
1123#if defined KDAB_EVAL
1124 EvalDialog::checkEvalLicense("KD Chart");
1125#endif
1126
1128 // no frame per default...
1129 // frameAttrs.setVisible( true );
1131 frameAttrs.setPadding(1);
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
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) {
1229 if (d->coordinatePlanes.count()) {
1230 if (!oldPlane) {
1231 oldPlane = d->coordinatePlanes.first();
1232 if (oldPlane == plane)
1233 return;
1234 }
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.
1257}
1258
1259void Chart::setGlobalLeading(int left, int top, int right, int bottom)
1260{
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
1315void Chart::paint(QPainter *painter, const QRect &target)
1316{
1317 if (target.isEmpty() || !painter) {
1318 return;
1319 }
1320
1323
1324 // Output on a widget
1325 if (dynamic_cast<QWidget *>(painter->device()) != nullptr) {
1327 qreal(target.height()) / qreal(geometry().size().height()));
1328 } else {
1329 // Output onto a QPixmap
1331
1332 const qreal resX = qreal(logicalDpiX()) / qreal(painter->device()->logicalDpiX());
1333 const qreal resY = qreal(logicalDpiY()) / qreal(painter->device()->logicalDpiY());
1334
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();
1347 if (differentSize) {
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
1378{
1379 d->isPlanesLayoutDirty = true;
1380 d->isFloatingLegendsLayoutDirty = true;
1382}
1383
1385{
1386 for (Legend *legend : qAsConst(d->legends)) {
1388 if (legend->position().isFloating() && !hidden) {
1389 // resize the legend
1390 const QSize legendSize(legend->sizeHint());
1392 // find the legends corner point (reference point plus any paddings)
1394 QPointF pt(relPos.calculatedPoint(size()));
1395 // qDebug() << pt;
1396 // calculate the legend's top left point
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
1416{
1417 QPainter painter(this);
1418 d->paintAll(&painter);
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);
1438 d, &Private::slotHeaderFooterPositionChanged);
1439
1440 // set the text attributes (why?)
1441
1442 TextAttributes textAttrs(hf->textAttributes());
1443 Measure measure(textAttrs.fontSize());
1444 measure.setRelativeMode(this, KDChartEnums::MeasureOrientationMinimum);
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{
1466 if (d->headerFooters.count()) {
1467 if (!oldHeaderFooter) {
1468 oldHeaderFooter = d->headerFooters.first();
1470 return;
1471 }
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
1496void 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
1516void 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);
1529}
1530
1531void Chart::addLegendInternal(Legend *legend, bool setMeasures)
1532{
1533 if (!legend) {
1534 return;
1535 }
1536
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) {
1557 Measure measure(textAttrs.fontSize());
1558 measure.setRelativeMode(this, KDChartEnums::MeasureOrientationMinimum);
1559 measure.setValue(20);
1560 textAttrs.setFontSize(measure);
1562
1564 measure.setRelativeMode(this, KDChartEnums::MeasureOrientationMinimum);
1565 measure.setValue(24);
1567
1569 legend->setReferenceArea(this);
1570 }
1571
1572 // add it to the appropriate layout
1573
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) {
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++) {
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) {
1611 sameAlignmentLayout->setContentsMargins(0, 0, 0, 0);
1612 }
1613
1614 sameAlignmentLayout->addItem(new MyWidgetItem(legend, legend->alignment()));
1615 }
1616
1618 d, &Private::slotUnregisterDestroyedLegend);
1620 d, &Private::slotLegendPositionChanged);
1622
1623 d->slotResizePlanes();
1624}
1625
1626void Chart::replaceLegend(Legend *legend, Legend *oldLegend_)
1627{
1628 if (legend && oldLegend_ != legend) {
1630 if (d->legends.count()) {
1631 if (!oldLegend) {
1632 oldLegend = d->legends.first();
1633 if (oldLegend == legend)
1634 return;
1635 }
1637 }
1638 delete oldLegend;
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();
1658}
1659
1661{
1662 return d->legends.isEmpty() ? 0 : d->legends.first();
1663}
1664
1666{
1667 return d->legends;
1668}
1669
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) {
1677 event->button(), event->buttons(), event->modifiers());
1678 plane->mousePressEvent(&ev);
1679 d->mouseClickedPlanes.append(plane);
1680 }
1681 }
1682}
1683
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) {
1691 event->button(), event->buttons(), event->modifiers());
1692 plane->mouseDoubleClickEvent(&ev);
1693 }
1694 }
1695}
1696
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
1715 QMouseEvent ev(QEvent::MouseMove, pos, event->globalPos(),
1716 event->button(), event->buttons(), event->modifiers());
1717 plane->mouseMoveEvent(&ev);
1718 }
1719}
1720
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
1740 event->button(), event->buttons(), event->modifiers());
1741 plane->mouseReleaseEvent(&ev);
1742 }
1743
1744 d->mouseClickedPlanes.clear();
1745}
1746
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
static const Qt::Alignment s_gridAlignments[3][3]
static QVector< LayoutGraphNode * > getPrioritySortedConnectedComponents(QVector< LayoutGraphNode * > &nodeList)
static void getRowAndColumnForPosition(KDChartEnums::PositionValue pos, int *row, int *column)
static void mergeNodeAxisInformation(LayoutGraphNode *lhs, LayoutGraphNode *rhs)
static void invalidateLayoutTree(QLayoutItem *item)
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
static void paintFrameAttributes(QPainter &painter, const QRect &rectangle, const KDChart::FrameAttributes &attributes)
static void paintBackgroundAttributes(QPainter &painter, const QRect &rectangle, const KDChart::BackgroundAttributes &attributes)
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.
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)
HeaderFooter * headerFooter()
void addLegend(Legend *legend)
LegendList legends() const
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()
void paint(QPainter *painter, const QRect &target)
bool useNewLayoutSystem
void takeLegend(Legend *legend)
void takeCoordinatePlane(AbstractCoordinatePlane *plane)
void setCoordinatePlaneLayout(QLayout *layout)
void insertCoordinatePlane(int index, AbstractCoordinatePlane *plane)
HeaderFooterList headerFooters()
Chart(QWidget *parent=nullptr)
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.
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.
void positionChanged(HeaderFooter *)
void destroyedHeaderFooter(HeaderFooter *)
Legend defines the interface for the legend drawing class.
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.
KDChartEnums::PositionValue value() const
static void setScaleFactor(const qreal scaleFactor)
Defines relative position information: reference area, position in this area (reference position),...
A text area in the chart with a background, a frame, etc.
A set of text attributes.
void setFontSize(const Measure &measure)
QList< AbstractCoordinatePlane * > CoordinatePlaneList
virtual void addItem(QLayoutItem *item) override
void addLayout(QLayout *layout, int stretch)
QChar fromAscii(char c)
QPoint pos()
MouseButtonPress
void addItem(QLayoutItem *item, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment)
void addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment)
void setColumnStretch(int column, int stretch)
void setRowStretch(int row, int stretch)
bool contains(const Key &key) const const
QHash::iterator insert(const Key &key, const T &value)
const QPoint & pos() const const
virtual int count() const const=0
virtual Qt::Orientations expandingDirections() const const override
virtual QRect geometry() const const override
virtual QLayoutItem * itemAt(int index) const const=0
virtual QSize maximumSize() const const override
virtual QSize minimumSize() const const override
bool setAlignment(QWidget *w, Qt::Alignment alignment)
void setContentsMargins(int left, int top, int right, int bottom)
Qt::Alignment alignment() const const
virtual Qt::Orientations expandingDirections() const const=0
virtual QRect geometry() const const=0
virtual bool hasHeightForWidth() const const
virtual void invalidate()
virtual QLayout * layout()
virtual QSize maximumSize() const const=0
virtual QSize minimumSize() const const=0
void setAlignment(Qt::Alignment alignment)
virtual QSize sizeHint() const const=0
void append(const T &value)
int size() const const
const char * className() const const
QVariant data(int role) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual const QMetaObject * metaObject() const const
T qobject_cast(QObject *object)
int logicalDpiX() const const
int logicalDpiY() const const
QPaintDevice * device() const const
void translate(const QPointF &offset)
int height() const const
bool isEmpty() const const
QSize size() const const
QPoint topLeft() const const
int width() const const
bool contains(const T &value) const const
QSet< T > fromList(const QList< T > &list)
QSet::iterator insert(const T &value)
void push(const T &t)
QString fromLatin1(const char *str, int size)
QString number(int n, int base)
typedef Alignment
ToolTipRole
typedef Orientations
WA_WState_ExplicitShowHide
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
void showText(const QPoint &pos, const QString &text, QWidget *w)
void append(const T &value)
QVector::iterator begin()
void clear()
bool contains(const T &value) const const
QVector::iterator end()
bool isEmpty() const const
int size() const const
virtual bool event(QEvent *event) override
virtual bool hasHeightForWidth() const const
virtual int heightForWidth(int w) const const
bool isHidden() const const
QLayout * layout() const const
QPoint mapFromGlobal(const QPoint &pos) const const
void move(int x, int y)
virtual void resizeEvent(QResizeEvent *event)
void setGeometry(int x, int y, int w, int h)
void setParent(QWidget *parent)
void show()
bool testAttribute(Qt::WidgetAttribute attribute) const const
void update()
virtual Qt::Orientations expandingDirections() const const override
virtual QRect geometry() const const override
virtual bool hasHeightForWidth() const const override
virtual int heightForWidth(int w) const const override
virtual bool isEmpty() const const override
virtual QSize maximumSize() const const override
virtual QSize minimumSize() const const override
virtual void setGeometry(const QRect &rect) override
virtual QSize sizeHint() const const override
virtual QWidget * widget() override

© 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 on Wed May 1 2024 00:01:10 for KD Chart API Documentation by doxygen 1.9.8