KD Chart API Documentation 3.0
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 Q_FOREACH (AbstractCoordinatePlane *p, 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 Q_FOREACH (LayoutGraphNode *node, nodeList)
332 visitedComponents[node] = Unknown;
333 for (int i = 0; i < nodeList.size(); ++i) {
334 LayoutGraphNode *curNode = nodeList[i];
335 LayoutGraphNode *representativeNode = curNode;
336 if (visitedComponents[curNode] != Visited) {
338 stack.push(curNode);
339 while (!stack.isEmpty()) {
340 curNode = stack.pop();
341 Q_ASSERT(visitedComponents[curNode] != Visited);
342 visitedComponents[curNode] = Visited;
343 if (curNode->bottomSuccesor && visitedComponents[curNode->bottomSuccesor] != Visited)
344 stack.push(curNode->bottomSuccesor);
345 if (curNode->leftSuccesor && visitedComponents[curNode->leftSuccesor] != Visited)
346 stack.push(curNode->leftSuccesor);
347 if (curNode->sharedSuccesor && visitedComponents[curNode->sharedSuccesor] != Visited)
348 stack.push(curNode->sharedSuccesor);
349 if (curNode->priority < representativeNode->priority)
350 representativeNode = curNode;
351 }
352 connectedComponents.append(representativeNode);
353 }
354 }
355 std::sort(connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator());
356 return connectedComponents;
357}
358
359struct PriorityComparator
360{
361public:
363 : m_mapping(mapping)
364 {
365 }
366 bool operator()(AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs) const
367 {
368 const LayoutGraphNode *lhsNode = m_mapping[lhs];
369 Q_ASSERT(lhsNode);
370 const LayoutGraphNode *rhsNode = m_mapping[rhs];
371 Q_ASSERT(rhsNode);
372 return lhsNode->priority < rhsNode->priority;
373 }
374
376};
377
378void checkExistingAxes(LayoutGraphNode *node)
379{
380 if (node && node->diagramPlane && node->diagramPlane->diagram()) {
381 auto *diag = qobject_cast<AbstractCartesianDiagram *>(node->diagramPlane->diagram());
382 if (diag) {
383 Q_FOREACH (const CartesianAxis *axis, diag->axes()) {
384 switch (axis->position()) {
385 case (CartesianAxis::Top):
386 node->topAxesLayout = true;
387 break;
389 node->bottomAxesLayout = true;
390 break;
391 case (CartesianAxis::Left):
392 node->leftAxesLayout = true;
393 break;
395 node->rightAxesLayout = true;
396 break;
397 }
398 }
399 }
400 }
401}
402
403static void mergeNodeAxisInformation(LayoutGraphNode *lhs, LayoutGraphNode *rhs)
404{
405 lhs->topAxesLayout |= rhs->topAxesLayout;
406 rhs->topAxesLayout = lhs->topAxesLayout;
407
408 lhs->bottomAxesLayout |= rhs->bottomAxesLayout;
409 rhs->bottomAxesLayout = lhs->bottomAxesLayout;
410
411 lhs->leftAxesLayout |= rhs->leftAxesLayout;
412 rhs->leftAxesLayout = lhs->leftAxesLayout;
413
414 lhs->rightAxesLayout |= rhs->rightAxesLayout;
415 rhs->rightAxesLayout = lhs->rightAxesLayout;
416}
417
419 const CoordinatePlaneList &list,
420 Chart::Private::AxisType type,
421 QVector<CartesianAxis *> *sharedAxes)
422{
423 if (!plane || !plane->diagram())
424 return CoordinatePlaneList();
425 Q_ASSERT(plane);
426 Q_ASSERT(plane->diagram());
427 CoordinatePlaneList result;
428 auto *diagram = qobject_cast<AbstractCartesianDiagram *>(plane->diagram());
429 if (!diagram)
430 return CoordinatePlaneList();
431
433 Q_FOREACH (CartesianAxis *axis, diagram->axes()) {
434 if ((type == Chart::Private::Ordinate && (axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right))
435 || (type == Chart::Private::Abscissa && (axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom))) {
436 axes.append(axis);
437 }
438 }
439 Q_FOREACH (AbstractCoordinatePlane *curPlane, list) {
440 auto *diagram =
441 qobject_cast<AbstractCartesianDiagram *>(curPlane->diagram());
442 if (!diagram)
443 continue;
444 Q_FOREACH (CartesianAxis *curSearchedAxis, axes) {
445 Q_FOREACH (CartesianAxis *curAxis, diagram->axes()) {
446 if (curSearchedAxis == curAxis) {
447 result.append(curPlane);
448 if (!sharedAxes->contains(curSearchedAxis))
449 sharedAxes->append(curSearchedAxis);
450 }
451 }
452 }
453 }
454
455 return result;
456}
457
464QVector<LayoutGraphNode *> Chart::Private::buildPlaneLayoutGraph()
465{
468 // create all nodes and a mapping between plane and nodes
469 Q_FOREACH (AbstractCoordinatePlane *curPlane, coordinatePlanes) {
470 if (curPlane->diagram()) {
471 allNodes.append(new LayoutGraphNode);
472 allNodes[allNodes.size() - 1]->diagramPlane = curPlane;
473 allNodes[allNodes.size() - 1]->priority = allNodes.size();
474 checkExistingAxes(allNodes[allNodes.size() - 1]);
475 planeNodeMapping[curPlane] = allNodes[allNodes.size() - 1];
476 }
477 }
478 // build the graph connections
479 Q_FOREACH (LayoutGraphNode *curNode, allNodes) {
480 QVector<CartesianAxis *> sharedAxes;
481 CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams(curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes);
482 Q_ASSERT(sharedAxes.size() < 2);
483 // TODO duplicated code make a method out of it
484 if (sharedAxes.size() == 1 && xSharedPlanes.size() > 1) {
485 // xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
486 // std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
487 for (int i = 0; i < xSharedPlanes.size() - 1; ++i) {
488 LayoutGraphNode *tmpNode = planeNodeMapping[xSharedPlanes[i]];
489 Q_ASSERT(tmpNode);
490 LayoutGraphNode *tmpNode2 = planeNodeMapping[xSharedPlanes[i + 1]];
491 Q_ASSERT(tmpNode2);
492 tmpNode->bottomSuccesor = tmpNode2;
493 }
494 // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
495 // {
496 // LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ];
497 // Q_ASSERT( lastNode );
498 // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
499 // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
500 // Q_ASSERT( ownerNode );
501 // lastNode->bottomSuccesor = ownerNode;
502 // }
503 // merge AxisInformation, needs a two pass run
504 LayoutGraphNode axisInfoNode;
505 for (int count = 0; count < 2; ++count) {
506 for (int i = 0; i < xSharedPlanes.size(); ++i) {
507 mergeNodeAxisInformation(&axisInfoNode, planeNodeMapping[xSharedPlanes[i]]);
508 }
509 }
510 }
511 sharedAxes.clear();
512 CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams(curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes);
513 Q_ASSERT(sharedAxes.size() < 2);
514 if (sharedAxes.size() == 1 && ySharedPlanes.size() > 1) {
515 // ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
516 // std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
517 for (int i = 0; i < ySharedPlanes.size() - 1; ++i) {
518 LayoutGraphNode *tmpNode = planeNodeMapping[ySharedPlanes[i]];
519 Q_ASSERT(tmpNode);
520 LayoutGraphNode *tmpNode2 = planeNodeMapping[ySharedPlanes[i + 1]];
521 Q_ASSERT(tmpNode2);
522 tmpNode->leftSuccesor = tmpNode2;
523 }
524 // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
525 // {
526 // LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ];
527 // Q_ASSERT( lastNode );
528 // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
529 // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
530 // Q_ASSERT( ownerNode );
531 // lastNode->bottomSuccesor = ownerNode;
532 // }
533 // merge AxisInformation, needs a two pass run
534 LayoutGraphNode axisInfoNode;
535 for (int count = 0; count < 2; ++count) {
536 for (int i = 0; i < ySharedPlanes.size(); ++i) {
537 mergeNodeAxisInformation(&axisInfoNode, planeNodeMapping[ySharedPlanes[i]]);
538 }
539 }
540 }
541 sharedAxes.clear();
542 if (curNode->diagramPlane->referenceCoordinatePlane())
543 curNode->sharedSuccesor = planeNodeMapping[curNode->diagramPlane->referenceCoordinatePlane()];
544 }
545
546 return allNodes;
547}
548
549QHash<AbstractCoordinatePlane *, PlaneInfo> Chart::Private::buildPlaneLayoutInfos()
550{
551 /* There are two ways in which planes can be caused to interact in
552 * where they are put layouting wise: The first is the reference plane. If
553 * such a reference plane is set, on a plane, it will use the same cell in the
554 * layout as that one. In addition to this, planes can share an axis. In that case
555 * they will be laid out in relation to each other as suggested by the position
556 * of the axis. If, for example Plane1 and Plane2 share an axis at position Left,
557 * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1
558 * also happens to be Plane2's reference plane, both planes are drawn over each
559 * other. The reference plane concept allows two planes to share the same space
560 * even if neither has any axis, and in case there are shared axis, it is used
561 * to decided, whether the planes should be painted on top of each other or
562 * laid out vertically or horizontally next to each other. */
565 Q_FOREACH (AbstractCoordinatePlane *plane, coordinatePlanes) {
566 PlaneInfo p;
567 // first check if we share space with another plane
568 p.referencePlane = plane->referenceCoordinatePlane();
569 planeInfos.insert(plane, p);
570
571 Q_FOREACH (AbstractDiagram *abstractDiagram, plane->diagrams()) {
572 auto *diagram =
573 qobject_cast<AbstractCartesianDiagram *>(abstractDiagram);
574 if (!diagram) {
575 continue;
576 }
577
578 Q_FOREACH (CartesianAxis *axis, diagram->axes()) {
579 if (!axisInfos.contains(axis)) {
580 /* If this is the first time we see this axis, add it, with the
581 * current plane. The first plane added to the chart that has
582 * the axis associated with it thus "owns" it, and decides about
583 * layout. */
584 AxisInfo i;
585 i.plane = plane;
586 axisInfos.insert(axis, i);
587 } else {
588 AxisInfo i = axisInfos[axis];
589 if (i.plane == plane) {
590 continue; // we don't want duplicates, only shared
591 }
592
593 /* The user expects diagrams to be added on top, and to the right
594 * so that horizontally we need to move the new diagram, vertically
595 * the reference one. */
596 PlaneInfo pi = planeInfos[plane];
597 // plane-to-plane linking overrides linking via axes
598 if (!pi.referencePlane) {
599 // we're not the first plane to see this axis, mark us as a slave
600 pi.referencePlane = i.plane;
601 if (axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right) {
602 pi.horizontalOffset += 1;
603 }
604 planeInfos[plane] = pi;
605
606 pi = planeInfos[i.plane];
607 if (axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom) {
608 pi.verticalOffset += 1;
609 }
610
611 planeInfos[i.plane] = pi;
612 }
613 }
614 }
615 }
616 // Create a new grid layout for each plane that has no reference.
617 p = planeInfos[plane];
618 if (p.referencePlane == nullptr) {
619 p.gridLayout = new QGridLayout();
620 p.gridLayout->setContentsMargins(0, 0, 0, 0);
621 planeInfos[plane] = p;
622 }
623 }
624 return planeInfos;
625}
626
627void Chart::Private::slotLayoutPlanes()
628{
629 /*TODO make sure this is really needed */
630 const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
632 if (planesLayout && dataAndLegendLayout)
633 dataAndLegendLayout->removeItem(planesLayout);
634
635 const bool hadPlanesLayout = planesLayout != nullptr;
636 int left, top, right, bottom;
637 if (hadPlanesLayout)
638 planesLayout->getContentsMargins(&left, &top, &right, &bottom);
639
640 Q_FOREACH (AbstractLayoutItem *plane, planeLayoutItems) {
641 plane->removeFromParentLayout();
642 }
643 // TODO they should get a correct parent, but for now it works
644 Q_FOREACH (AbstractLayoutItem *plane, planeLayoutItems) {
645 if (dynamic_cast<AutoSpacerLayoutItem *>(plane))
646 delete plane;
647 }
648
649 planeLayoutItems.clear();
650 delete planesLayout;
651 // hint: The direction is configurable by the user now, as
652 // we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25)
653 planesLayout = new QBoxLayout(oldPlanesDirection);
654
655 isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting
656
657 if (useNewLayoutSystem) {
658 gridPlaneLayout = new QGridLayout;
659 planesLayout->addLayout(gridPlaneLayout);
660
661 if (hadPlanesLayout)
662 planesLayout->setContentsMargins(left, top, right, bottom);
663 planesLayout->setObjectName(QString::fromLatin1("planesLayout"));
664
665 /* First go through all planes and all axes and figure out whether the planes
666 * need to coordinate. If they do, they share a grid layout, if not, each
667 * get their own. See buildPlaneLayoutInfos() for more details. */
668
669 QVector<LayoutGraphNode *> vals = buildPlaneLayoutGraph();
670 // qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size();
672 // qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size();
673 int row = 0;
674 int col = 0;
675 QSet<CartesianAxis *> laidOutAxes;
676 for (int i = 0; i < connectedComponents.size(); ++i) {
677 LayoutGraphNode *curComponent = connectedComponents[i];
678 for (LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor) {
679 col = 0;
680 for (LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor) {
681 Q_ASSERT(curColComponent->diagramPlane->diagrams().size() == 1);
682 Q_FOREACH (AbstractDiagram *diagram, curColComponent->diagramPlane->diagrams()) {
683 const int planeRowOffset = 1; // curColComponent->topAxesLayout ? 1 : 0;
684 const int planeColOffset = 1; // curColComponent->leftAxesLayout ? 1 : 0;
685 // qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset;
686
687 // qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset;
688 planeLayoutItems << curColComponent->diagramPlane;
689 auto *cartDiag = qobject_cast<AbstractCartesianDiagram *>(diagram);
690 if (cartDiag) {
691 gridPlaneLayout->addItem(curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2);
692 curColComponent->diagramPlane->setParentLayout(gridPlaneLayout);
693 QHBoxLayout *leftLayout = nullptr;
694 QHBoxLayout *rightLayout = nullptr;
695 QVBoxLayout *topLayout = nullptr;
696 QVBoxLayout *bottomLayout = nullptr;
697 if (curComponent->sharedSuccesor) {
698 gridPlaneLayout->addItem(curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2);
699 curColComponent->sharedSuccesor->diagramPlane->setParentLayout(gridPlaneLayout);
700 planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane;
701 }
702 Q_FOREACH (CartesianAxis *axis, cartDiag->axes()) {
703 if (axis->isAbscissa()) {
704 if (curColComponent->bottomSuccesor)
705 continue;
706 }
707 if (laidOutAxes.contains(axis))
708 continue;
709 // if ( axis->diagram() != diagram )
710 // continue;
711 switch (axis->position()) {
712 case (CartesianAxis::Top):
713 if (!topLayout)
714 topLayout = new QVBoxLayout;
715 topLayout->addItem(axis);
716 axis->setParentLayout(topLayout);
717 break;
719 if (!bottomLayout)
720 bottomLayout = new QVBoxLayout;
721 bottomLayout->addItem(axis);
722 axis->setParentLayout(bottomLayout);
723 break;
724 case (CartesianAxis::Left):
725 if (!leftLayout)
726 leftLayout = new QHBoxLayout;
727 leftLayout->addItem(axis);
728 axis->setParentLayout(leftLayout);
729 break;
731 if (!rightLayout) {
732 rightLayout = new QHBoxLayout;
733 }
734 rightLayout->addItem(axis);
735 axis->setParentLayout(rightLayout);
736 break;
737 }
738 planeLayoutItems << axis;
739 laidOutAxes.insert(axis);
740 }
741 if (leftLayout)
742 gridPlaneLayout->addLayout(leftLayout, row + planeRowOffset, col, 2, 1,
744 if (rightLayout)
745 gridPlaneLayout->addLayout(rightLayout, row, col + planeColOffset + 2, 2, 1,
747 if (topLayout)
748 gridPlaneLayout->addLayout(topLayout, row, col + planeColOffset, 1, 2,
750 if (bottomLayout)
751 gridPlaneLayout->addLayout(bottomLayout, row + planeRowOffset + 2,
752 col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter);
753 } else {
754 gridPlaneLayout->addItem(curColComponent->diagramPlane, row, col, 4, 4);
755 curColComponent->diagramPlane->setParentLayout(gridPlaneLayout);
756 }
757 col += planeColOffset + 2 + (1);
758 }
759 }
760 int axisOffset = 2; // curRowComponent->topAxesLayout ? 1 : 0;
761 // axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0;
762 const int rowOffset = axisOffset + 2;
763 row += rowOffset;
764 }
765
766 // if ( planesLayout->direction() == QBoxLayout::TopToBottom )
767 // ++row;
768 // else
769 // ++col;
770 }
771
772 qDeleteAll(vals);
773 // re-add our grid(s) to the chart's layout
774 if (dataAndLegendLayout) {
775 dataAndLegendLayout->addLayout(planesLayout, 1, 1);
776 dataAndLegendLayout->setRowStretch(1, 1000);
777 dataAndLegendLayout->setColumnStretch(1, 1000);
778 }
779 slotResizePlanes();
780#ifdef NEW_LAYOUT_DEBUG
781 for (int i = 0; i < gridPlaneLayout->rowCount(); ++i) {
782 for (int j = 0; j < gridPlaneLayout->columnCount(); ++j) {
783 if (gridPlaneLayout->itemAtPosition(i, j))
784 qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition(i, j)->geometry();
785 else
786 qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present";
787 }
788 }
789 // qDebug() << Q_FUNC_INFO << "Relayout ended";
790#endif
791 } else {
792 if (hadPlanesLayout) {
793 planesLayout->setContentsMargins(left, top, right, bottom);
794 }
795
796 planesLayout->setContentsMargins(0, 0, 0, 0);
797 planesLayout->setSpacing(0);
798 planesLayout->setObjectName(QString::fromLatin1("planesLayout"));
799
800 /* First go through all planes and all axes and figure out whether the planes
801 * need to coordinate. If they do, they share a grid layout, if not, each
802 * gets their own. See buildPlaneLayoutInfos() for more details. */
803 QHash<AbstractCoordinatePlane *, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
805 Q_FOREACH (AbstractCoordinatePlane *plane, coordinatePlanes) {
806 Q_ASSERT(planeInfos.contains(plane));
807 PlaneInfo &pi = planeInfos[plane];
808 const int column = pi.horizontalOffset;
809 const int row = pi.verticalOffset;
810 // qDebug() << "processing plane at column" << column << "and row" << row;
811 QGridLayout *planeLayout = pi.gridLayout;
812
813 if (!planeLayout) {
814 PlaneInfo &refPi = pi;
815 // if this plane is sharing an axis with another one, recursively check for the original plane and use
816 // the grid of that as planeLayout.
817 while (!planeLayout && refPi.referencePlane) {
818 refPi = planeInfos[refPi.referencePlane];
819 planeLayout = refPi.gridLayout;
820 }
821 Q_ASSERT_X(planeLayout,
822 "Chart::Private::slotLayoutPlanes()",
823 "Invalid reference plane. Please check that the reference plane has been added to the Chart.");
824 } else {
825 planesLayout->addLayout(planeLayout);
826 }
827
828 /* Put the plane in the center of the layout. If this is our own, that's
829 * the middle of the layout, if we are sharing, it's a cell in the center
830 * column of the shared grid. */
831 planeLayoutItems << plane;
832 plane->setParentLayout(planeLayout);
833 planeLayout->addItem(plane, row, column, 1, 1, {});
834 // qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
835 planeLayout->setRowStretch(row, 2);
836 planeLayout->setColumnStretch(column, 2);
837 Q_FOREACH (AbstractDiagram *abstractDiagram, plane->diagrams()) {
838 auto *diagram =
839 qobject_cast<AbstractCartesianDiagram *>(abstractDiagram);
840 if (!diagram) {
841 continue; // FIXME what about polar ?
842 }
843
844 if (pi.referencePlane != nullptr) {
845 pi.topAxesLayout = planeInfos[pi.referencePlane].topAxesLayout;
846 pi.bottomAxesLayout = planeInfos[pi.referencePlane].bottomAxesLayout;
847 pi.leftAxesLayout = planeInfos[pi.referencePlane].leftAxesLayout;
848 pi.rightAxesLayout = planeInfos[pi.referencePlane].rightAxesLayout;
849 }
850
851 // collect all axes of a kind into sublayouts
852 if (pi.topAxesLayout == nullptr) {
853 pi.topAxesLayout = new QVBoxLayout;
854 pi.topAxesLayout->setContentsMargins(0, 0, 0, 0);
855 pi.topAxesLayout->setObjectName(QString::fromLatin1("topAxesLayout"));
856 }
857 if (pi.bottomAxesLayout == nullptr) {
858 pi.bottomAxesLayout = new QVBoxLayout;
859 pi.bottomAxesLayout->setContentsMargins(0, 0, 0, 0);
860 pi.bottomAxesLayout->setObjectName(QString::fromLatin1("bottomAxesLayout"));
861 }
862 if (pi.leftAxesLayout == nullptr) {
863 pi.leftAxesLayout = new QHBoxLayout;
864 pi.leftAxesLayout->setContentsMargins(0, 0, 0, 0);
865 pi.leftAxesLayout->setObjectName(QString::fromLatin1("leftAxesLayout"));
866 }
867 if (pi.rightAxesLayout == nullptr) {
868 pi.rightAxesLayout = new QHBoxLayout;
869 pi.rightAxesLayout->setContentsMargins(0, 0, 0, 0);
870 pi.rightAxesLayout->setObjectName(QString::fromLatin1("rightAxesLayout"));
871 }
872
873 if (pi.referencePlane != nullptr) {
874 planeInfos[pi.referencePlane].topAxesLayout = pi.topAxesLayout;
875 planeInfos[pi.referencePlane].bottomAxesLayout = pi.bottomAxesLayout;
876 planeInfos[pi.referencePlane].leftAxesLayout = pi.leftAxesLayout;
877 planeInfos[pi.referencePlane].rightAxesLayout = pi.rightAxesLayout;
878 }
879
880 // pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
881 Q_FOREACH (CartesianAxis *axis, diagram->axes()) {
882 if (axisInfos.contains(axis)) {
883 continue; // already laid out this one
884 }
885 Q_ASSERT(axis);
886 axis->setCachedSizeDirty();
887 // qDebug() << "--------------- axis added to planeLayoutItems -----------------";
888 planeLayoutItems << axis;
889
890 switch (axis->position()) {
892 axis->setParentLayout(pi.topAxesLayout);
893 pi.topAxesLayout->addItem(axis);
894 break;
896 axis->setParentLayout(pi.bottomAxesLayout);
897 pi.bottomAxesLayout->addItem(axis);
898 break;
900 axis->setParentLayout(pi.leftAxesLayout);
901 pi.leftAxesLayout->addItem(axis);
902 break;
904 axis->setParentLayout(pi.rightAxesLayout);
905 pi.rightAxesLayout->addItem(axis);
906 break;
907 default:
908 Q_ASSERT_X(false, "Chart::paintEvent", "unknown axis position");
909 break;
910 };
911 axisInfos.insert(axis, AxisInfo());
912 }
913 /* Put each stack of axes-layouts in the cells surrounding the
914 * associated plane. We are laying out in the order the planes
915 * were added, and the first one gets to lay out shared axes.
916 * Private axes go here as well, of course. */
917
918 if (!pi.topAxesLayout->parent()) {
919 planeLayout->addLayout(pi.topAxesLayout, row - 1, column);
920 }
921 if (!pi.bottomAxesLayout->parent()) {
922 planeLayout->addLayout(pi.bottomAxesLayout, row + 1, column);
923 }
924 if (!pi.leftAxesLayout->parent()) {
925 planeLayout->addLayout(pi.leftAxesLayout, row, column - 1);
926 }
927 if (!pi.rightAxesLayout->parent()) {
928 planeLayout->addLayout(pi.rightAxesLayout, row, column + 1);
929 }
930 }
931
932 // use up to four auto-spacer items in the corners around the diagrams:
933#define ADD_AUTO_SPACER_IF_NEEDED( \
934 spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout) \
935 { \
936 if (hLayout || vLayout) { \
937 AutoSpacerLayoutItem *spacer = new AutoSpacerLayoutItem(hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout); \
938 planeLayout->addItem(spacer, spacerRow, spacerColumn, 1, 1); \
939 spacer->setParentLayout(planeLayout); \
940 planeLayoutItems << spacer; \
941 } \
942 }
943
944 if (plane->isCornerSpacersEnabled()) {
945 ADD_AUTO_SPACER_IF_NEEDED(row - 1, column - 1, false, pi.leftAxesLayout, false, pi.topAxesLayout)
946 ADD_AUTO_SPACER_IF_NEEDED(row + 1, column - 1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout)
947 ADD_AUTO_SPACER_IF_NEEDED(row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout)
948 ADD_AUTO_SPACER_IF_NEEDED(row + 1, column + 1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout)
949 }
950 }
951 // re-add our grid(s) to the chart's layout
952 if (dataAndLegendLayout) {
953 dataAndLegendLayout->addLayout(planesLayout, 1, 1);
954 dataAndLegendLayout->setRowStretch(1, 1000);
955 dataAndLegendLayout->setColumnStretch(1, 1000);
956 }
957
958 slotResizePlanes();
959 }
960}
961
962void Chart::Private::createLayouts()
963{
964 // The toplevel layout provides the left and right global margins
965 layout = new QHBoxLayout(chart);
966 layout->setContentsMargins(0, 0, 0, 0);
967 layout->setObjectName(QString::fromLatin1("Chart::Private::layout"));
968 layout->addSpacing(globalLeadingLeft);
969 leftOuterSpacer = layout->itemAt(layout->count() - 1)->spacerItem();
970
971 // The vLayout provides top and bottom global margins and lays
972 // out headers, footers and the diagram area.
973 vLayout = new QVBoxLayout();
974 vLayout->setContentsMargins(0, 0, 0, 0);
975 vLayout->setObjectName(QString::fromLatin1("vLayout"));
976
977 layout->addLayout(vLayout, 1000);
978 layout->addSpacing(globalLeadingRight);
979 rightOuterSpacer = layout->itemAt(layout->count() - 1)->spacerItem();
980
981 // 1. the spacing above the header area
982 vLayout->addSpacing(globalLeadingTop);
983 topOuterSpacer = vLayout->itemAt(vLayout->count() - 1)->spacerItem();
984 // 2. the header area
985 headerLayout = new QGridLayout();
986 headerLayout->setContentsMargins(0, 0, 0, 0);
987 vLayout->addLayout(headerLayout);
988 // 3. the area containing coordinate planes, axes, and legends
989 dataAndLegendLayout = new QGridLayout();
990 dataAndLegendLayout->setContentsMargins(0, 0, 0, 0);
991 dataAndLegendLayout->setObjectName(QString::fromLatin1("dataAndLegendLayout"));
992 vLayout->addLayout(dataAndLegendLayout, 1000);
993 // 4. the footer area
994 footerLayout = new QGridLayout();
995 footerLayout->setContentsMargins(0, 0, 0, 0);
996 footerLayout->setObjectName(QString::fromLatin1("footerLayout"));
997 vLayout->addLayout(footerLayout);
998
999 // 5. Prepare the header / footer layout cells:
1000 // Each of the 9 header cells (the 9 footer cells)
1001 // contain their own QVBoxLayout
1002 // since there can be more than one header (footer) per cell.
1003 for (int row = 0; row < 3; ++row) {
1004 for (int column = 0; column < 3; ++column) {
1005 const Qt::Alignment align = s_gridAlignments[row][column];
1006 for (int headOrFoot = 0; headOrFoot < 2; headOrFoot++) {
1007 auto *innerLayout = new QVBoxLayout();
1008 innerLayout->setContentsMargins(0, 0, 0, 0);
1009 innerLayout->setAlignment(align);
1010 innerHdFtLayouts[headOrFoot][row][column] = innerLayout;
1011
1012 QGridLayout *outerLayout = headOrFoot == 0 ? headerLayout : footerLayout;
1013 outerLayout->addLayout(innerLayout, row, column, align);
1014 }
1015 }
1016 }
1017
1018 // 6. the spacing below the footer area
1019 vLayout->addSpacing(globalLeadingBottom);
1020 bottomOuterSpacer = vLayout->itemAt(vLayout->count() - 1)->spacerItem();
1021
1022 // the data+axes area
1023 dataAndLegendLayout->addLayout(planesLayout, 1, 1);
1024 dataAndLegendLayout->setRowStretch(1, 1);
1025 dataAndLegendLayout->setColumnStretch(1, 1);
1026}
1027
1028void Chart::Private::slotResizePlanes()
1029{
1030 if (!dataAndLegendLayout) {
1031 return;
1032 }
1033 if (!overrideSize.isValid()) {
1034 // activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize
1035 // is set. So don't let the layout grab the wrong size in that case.
1036 // When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ),
1037 // which also "activates" the layout in the sense that it distributes space internally.
1038 layout->activate();
1039 }
1040 // Adapt diagram drawing to the new size
1041 Q_FOREACH (AbstractCoordinatePlane *plane, coordinatePlanes) {
1042 plane->layoutDiagrams();
1043 }
1044}
1045
1046void Chart::Private::updateDirtyLayouts()
1047{
1048 if (isPlanesLayoutDirty) {
1049 Q_FOREACH (AbstractCoordinatePlane *p, coordinatePlanes) {
1051 p->layoutPlanes();
1052 p->layoutDiagrams();
1053 }
1054 }
1055 if (isPlanesLayoutDirty || isFloatingLegendsLayoutDirty) {
1056 chart->reLayoutFloatingLegends();
1057 }
1058 isPlanesLayoutDirty = false;
1059 isFloatingLegendsLayoutDirty = false;
1060}
1061
1062void Chart::Private::reapplyInternalLayouts()
1063{
1064 QRect geo = layout->geometry();
1065
1066 invalidateLayoutTree(layout);
1067 layout->setGeometry(geo);
1068 slotResizePlanes();
1069}
1070
1071void Chart::Private::paintAll(QPainter *painter)
1072{
1073 updateDirtyLayouts();
1074
1075 QRect rect(QPoint(0, 0), overrideSize.isValid() ? overrideSize : chart->size());
1076
1077 // qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
1078
1079 // Paint the background (if any)
1080 AbstractAreaBase::paintBackgroundAttributes(*painter, rect, backgroundAttributes);
1081 // Paint the frame (if any)
1082 AbstractAreaBase::paintFrameAttributes(*painter, rect, frameAttributes);
1083
1084 chart->reLayoutFloatingLegends();
1085
1086 Q_FOREACH (AbstractLayoutItem *planeLayoutItem, planeLayoutItems) {
1087 planeLayoutItem->paintAll(*painter);
1088 }
1089 Q_FOREACH (TextArea *textLayoutItem, textLayoutItems) {
1090 textLayoutItem->paintAll(*painter);
1091 }
1092 Q_FOREACH (Legend *legend, legends) {
1093 const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
1094 if (!hidden) {
1095 // qDebug() << "painting legend at " << legend->geometry();
1096 legend->paintIntoRect(*painter, legend->geometry());
1097 }
1098 }
1099}
1100
1101// ******** Chart interface implementation ***********
1102
1103#define d d_func()
1104
1106 : QWidget(parent)
1107 , _d(new Private(this))
1108{
1109#if defined KDAB_EVAL
1110 EvalDialog::checkEvalLicense("KD Chart");
1111#endif
1112
1114 // no frame per default...
1115 // frameAttrs.setVisible( true );
1117 frameAttrs.setPadding(1);
1119
1121
1122 d->createLayouts();
1123}
1124
1126{
1127 delete d;
1128}
1129
1131{
1132 d->frameAttributes = a;
1133}
1134
1136{
1137 return d->frameAttributes;
1138}
1139
1141{
1142 d->backgroundAttributes = a;
1143}
1144
1146{
1147 return d->backgroundAttributes;
1148}
1149
1150// TODO KDChart 3.0; change QLayout into QBoxLayout::Direction
1152{
1153 if (layout == d->planesLayout)
1154 return;
1155 if (d->planesLayout) {
1156 // detach all QLayoutItem's the previous planesLayout has cause
1157 // otherwise deleting the planesLayout would delete them too.
1158 for (int i = d->planesLayout->count() - 1; i >= 0; --i) {
1159 d->planesLayout->takeAt(i);
1160 }
1161 delete d->planesLayout;
1162 }
1163 d->planesLayout = qobject_cast<QBoxLayout *>(layout);
1164 d->slotLayoutPlanes();
1165}
1166
1168{
1169 return d->planesLayout;
1170}
1171
1173{
1174 if (d->coordinatePlanes.isEmpty()) {
1175 qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
1176 return nullptr;
1177 } else {
1178 return d->coordinatePlanes.first();
1179 }
1180}
1181
1183{
1184 return d->coordinatePlanes;
1185}
1186
1188{
1189 // Append
1190 insertCoordinatePlane(d->coordinatePlanes.count(), plane);
1191}
1192
1194{
1195 if (index < 0 || index > d->coordinatePlanes.count()) {
1196 return;
1197 }
1198
1199 connect(plane, SIGNAL(destroyedCoordinatePlane(AbstractCoordinatePlane *)),
1201 connect(plane, SIGNAL(needUpdate()), this, SLOT(update()));
1202 connect(plane, SIGNAL(needRelayout()), d, SLOT(slotResizePlanes()));
1203 connect(plane, SIGNAL(needLayoutPlanes()), d, SLOT(slotLayoutPlanes()));
1205 d->coordinatePlanes.insert(index, plane);
1206 plane->setParent(this);
1207 d->slotLayoutPlanes();
1208}
1209
1211 AbstractCoordinatePlane *oldPlane_)
1212{
1213 if (plane && oldPlane_ != plane) {
1215 if (d->coordinatePlanes.count()) {
1216 if (!oldPlane) {
1217 oldPlane = d->coordinatePlanes.first();
1218 if (oldPlane == plane)
1219 return;
1220 }
1222 }
1223 delete oldPlane;
1224 addCoordinatePlane(plane);
1225 }
1226}
1227
1229{
1230 const int idx = d->coordinatePlanes.indexOf(plane);
1231 if (idx != -1) {
1232 d->coordinatePlanes.takeAt(idx);
1233 disconnect(plane, nullptr, d, nullptr);
1234 disconnect(plane, nullptr, this, nullptr);
1235 plane->removeFromParentLayout();
1236 plane->setParent(nullptr);
1237 d->mouseClickedPlanes.removeAll(plane);
1238 }
1239 d->slotLayoutPlanes();
1240 // Need to emit the signal: In case somebody has connected the signal
1241 // to her own slot for e.g. calling update() on a widget containing the chart.
1243}
1244
1245void Chart::setGlobalLeading(int left, int top, int right, int bottom)
1246{
1249 setGlobalLeadingRight(right);
1250 setGlobalLeadingBottom(bottom);
1251}
1252
1254{
1255 d->globalLeadingLeft = leading;
1256 d->leftOuterSpacer->changeSize(leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
1257 d->reapplyInternalLayouts();
1258}
1259
1261{
1262 return d->globalLeadingLeft;
1263}
1264
1266{
1267 d->globalLeadingTop = leading;
1268 d->topOuterSpacer->changeSize(0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed);
1269 d->reapplyInternalLayouts();
1270}
1271
1273{
1274 return d->globalLeadingTop;
1275}
1276
1278{
1279 d->globalLeadingRight = leading;
1280 d->rightOuterSpacer->changeSize(leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
1281 d->reapplyInternalLayouts();
1282}
1283
1285{
1286 return d->globalLeadingRight;
1287}
1288
1290{
1291 d->globalLeadingBottom = leading;
1292 d->bottomOuterSpacer->changeSize(0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed);
1293 d->reapplyInternalLayouts();
1294}
1295
1297{
1298 return d->globalLeadingBottom;
1299}
1300
1301void Chart::paint(QPainter *painter, const QRect &target)
1302{
1303 if (target.isEmpty() || !painter) {
1304 return;
1305 }
1306
1309
1310 // Output on a widget
1311 if (dynamic_cast<QWidget *>(painter->device()) != nullptr) {
1313 qreal(target.height()) / qreal(geometry().size().height()));
1314 } else {
1315 // Output onto a QPixmap
1317
1318 const qreal resX = qreal(logicalDpiX()) / qreal(painter->device()->logicalDpiX());
1319 const qreal resY = qreal(logicalDpiY()) / qreal(painter->device()->logicalDpiY());
1320
1322 qreal(target.height()) / qreal(geometry().size().height()) * resY);
1323 }
1324
1325 const QPoint translation = target.topLeft();
1326 painter->translate(translation);
1327
1328 // the following layout logic has the disadvantage that repeatedly calling this method can
1329 // cause a relayout every time, but since this method's main use seems to be printing, the
1330 // gratuitous relayouts shouldn't be much of a performance problem.
1331 const bool differentSize = target.size() != size();
1333 if (differentSize) {
1335 d->isPlanesLayoutDirty = true;
1336 d->isFloatingLegendsLayoutDirty = true;
1337 invalidateLayoutTree(d->dataAndLegendLayout);
1338 d->dataAndLegendLayout->setGeometry(QRect(QPoint(), target.size()));
1339 }
1340
1341 d->overrideSize = target.size();
1342 d->paintAll(painter);
1343 d->overrideSize = QSize();
1344
1345 if (differentSize) {
1346 invalidateLayoutTree(d->dataAndLegendLayout);
1347 d->dataAndLegendLayout->setGeometry(oldGeometry);
1348 d->isPlanesLayoutDirty = true;
1349 d->isFloatingLegendsLayoutDirty = true;
1350 }
1351
1352 // for debugging
1353 // painter->setPen( QPen( Qt::blue, 8 ) );
1354 // painter->drawRect( target );
1355
1356 painter->translate(-translation.x(), -translation.y());
1357
1361}
1362
1364{
1365 d->isPlanesLayoutDirty = true;
1366 d->isFloatingLegendsLayoutDirty = true;
1368}
1369
1371{
1372 Q_FOREACH (Legend *legend, d->legends) {
1374 if (legend->position().isFloating() && !hidden) {
1375 // resize the legend
1376 const QSize legendSize(legend->sizeHint());
1378 // find the legends corner point (reference point plus any paddings)
1380 QPointF pt(relPos.calculatedPoint(size()));
1381 // qDebug() << pt;
1382 // calculate the legend's top left point
1384 if ((relPos.alignment() & alignTopLeft) != alignTopLeft) {
1385 if (relPos.alignment() & Qt::AlignRight)
1386 pt.rx() -= legendSize.width();
1387 else if (relPos.alignment() & Qt::AlignHCenter)
1388 pt.rx() -= 0.5 * legendSize.width();
1389
1390 if (relPos.alignment() & Qt::AlignBottom)
1391 pt.ry() -= legendSize.height();
1392 else if (relPos.alignment() & Qt::AlignVCenter)
1393 pt.ry() -= 0.5 * legendSize.height();
1394 }
1395 // qDebug() << pt << endl;
1396 legend->move(static_cast<int>(pt.x()), static_cast<int>(pt.y()));
1397 }
1398 }
1399}
1400
1402{
1403 QPainter painter(this);
1404 d->paintAll(&painter);
1406}
1407
1409{
1410 Q_ASSERT(hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer);
1411 int row;
1412 int column;
1413 getRowAndColumnForPosition(hf->position().value(), &row, &column);
1414 if (row == -1) {
1415 qWarning("Unknown header/footer position");
1416 return;
1417 }
1418
1419 d->headerFooters.append(hf);
1420 d->textLayoutItems.append(hf);
1421 connect(hf, SIGNAL(destroyedHeaderFooter(HeaderFooter *)),
1423 connect(hf, SIGNAL(positionChanged(HeaderFooter *)),
1425
1426 // set the text attributes (why?)
1427
1428 TextAttributes textAttrs(hf->textAttributes());
1429 Measure measure(textAttrs.fontSize());
1430 measure.setRelativeMode(this, KDChartEnums::MeasureOrientationMinimum);
1431 measure.setValue(20);
1432 textAttrs.setFontSize(measure);
1433 hf->setTextAttributes(textAttrs);
1434
1435 // add it to the appropriate layout
1436
1437 int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1;
1438 QVBoxLayout *headerFooterLayout = d->innerHdFtLayouts[innerLayoutIdx][row][column];
1439
1440 hf->setParentLayout(headerFooterLayout);
1441 hf->setAlignment(s_gridAlignments[row][column]);
1442 headerFooterLayout->addItem(hf);
1443
1444 d->slotResizePlanes();
1445}
1446
1448 HeaderFooter *oldHeaderFooter_)
1449{
1452 if (d->headerFooters.count()) {
1453 if (!oldHeaderFooter) {
1454 oldHeaderFooter = d->headerFooters.first();
1456 return;
1457 }
1459 }
1460 delete oldHeaderFooter;
1462 }
1463}
1464
1466{
1467 const int idx = d->headerFooters.indexOf(headerFooter);
1468 if (idx == -1) {
1469 return;
1470 }
1471 disconnect(headerFooter, SIGNAL(destroyedHeaderFooter(HeaderFooter *)),
1473
1474 d->headerFooters.takeAt(idx);
1476 headerFooter->setParentLayout(nullptr);
1477 d->textLayoutItems.remove(d->textLayoutItems.indexOf(headerFooter));
1478
1479 d->slotResizePlanes();
1480}
1481
1482void Chart::Private::slotHeaderFooterPositionChanged(HeaderFooter *hf)
1483{
1484 chart->takeHeaderFooter(hf);
1485 chart->addHeaderFooter(hf);
1486}
1487
1489{
1490 if (d->headerFooters.isEmpty()) {
1491 return nullptr;
1492 } else {
1493 return d->headerFooters.first();
1494 }
1495}
1496
1498{
1499 return d->headerFooters;
1500}
1501
1502void Chart::Private::slotLegendPositionChanged(AbstractAreaWidget *aw)
1503{
1504 auto *legend = qobject_cast<Legend *>(aw);
1505 Q_ASSERT(legend);
1506 chart->takeLegend(legend);
1507 chart->addLegendInternal(legend, false);
1508}
1509
1511{
1512 legend->show();
1513 addLegendInternal(legend, true);
1515}
1516
1517void Chart::addLegendInternal(Legend *legend, bool setMeasures)
1518{
1519 if (!legend) {
1520 return;
1521 }
1522
1525 qWarning("Not showing legend because PositionCenter is not supported for legends.");
1526 }
1527
1528 int row;
1529 int column;
1530 getRowAndColumnForPosition(pos, &row, &column);
1531 if (row < 0 && pos != KDChartEnums::PositionFloating) {
1532 qWarning("Not showing legend because of unknown legend position.");
1533 return;
1534 }
1535
1536 d->legends.append(legend);
1537 legend->setParent(this);
1538
1539 // set text attributes (why?)
1540
1541 if (setMeasures) {
1543 Measure measure(textAttrs.fontSize());
1544 measure.setRelativeMode(this, KDChartEnums::MeasureOrientationMinimum);
1545 measure.setValue(20);
1546 textAttrs.setFontSize(measure);
1548
1550 measure.setRelativeMode(this, KDChartEnums::MeasureOrientationMinimum);
1551 measure.setValue(24);
1553
1555 legend->setReferenceArea(this);
1556 }
1557
1558 // add it to the appropriate layout
1559
1562
1563 // in each edge and corner of the outer layout, there's a grid for the different alignments that we create
1564 // on demand. we don't remove it when empty.
1565
1566 QLayoutItem *edgeItem = d->dataAndLegendLayout->itemAtPosition(row, column);
1567 auto *alignmentsLayout = dynamic_cast<QGridLayout *>(edgeItem);
1568 Q_ASSERT(!edgeItem || alignmentsLayout); // if it exists, it must be a QGridLayout
1569 if (!alignmentsLayout) {
1571 d->dataAndLegendLayout->addLayout(alignmentsLayout, row, column);
1572 alignmentsLayout->setContentsMargins(0, 0, 0, 0);
1573 }
1574
1575 // in case there are several legends in the same edge or corner with the same alignment, they are stacked
1576 // vertically using a QVBoxLayout. it is created on demand as above.
1577
1578 row = 1;
1579 column = 1;
1580 for (int i = 0; i < 3; i++) {
1581 for (int j = 0; j < 3; j++) {
1583 if (align == legend->alignment()) {
1584 row = i;
1585 column = j;
1586 break;
1587 }
1588 }
1589 }
1590
1591 QLayoutItem *alignmentItem = alignmentsLayout->itemAtPosition(row, column);
1592 auto *sameAlignmentLayout = dynamic_cast<QVBoxLayout *>(alignmentItem);
1593 Q_ASSERT(!alignmentItem || sameAlignmentLayout); // if it exists, it must be a QVBoxLayout
1594 if (!sameAlignmentLayout) {
1597 sameAlignmentLayout->setContentsMargins(0, 0, 0, 0);
1598 }
1599
1600 sameAlignmentLayout->addItem(new MyWidgetItem(legend, legend->alignment()));
1601 }
1602
1603 connect(legend, SIGNAL(destroyedLegend(Legend *)),
1605 connect(legend, SIGNAL(positionChanged(AbstractAreaWidget *)),
1608
1609 d->slotResizePlanes();
1610}
1611
1612void Chart::replaceLegend(Legend *legend, Legend *oldLegend_)
1613{
1614 if (legend && oldLegend_ != legend) {
1616 if (d->legends.count()) {
1617 if (!oldLegend) {
1618 oldLegend = d->legends.first();
1619 if (oldLegend == legend)
1620 return;
1621 }
1623 }
1624 delete oldLegend;
1626 }
1627}
1628
1630{
1631 const int idx = d->legends.indexOf(legend);
1632 if (idx == -1) {
1633 return;
1634 }
1635
1636 d->legends.takeAt(idx);
1637 disconnect(legend, nullptr, d, nullptr);
1638 disconnect(legend, nullptr, this, nullptr);
1639 // the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout)
1640 legend->setParent(nullptr);
1641
1642 d->slotResizePlanes();
1644}
1645
1647{
1648 return d->legends.isEmpty() ? 0 : d->legends.first();
1649}
1650
1652{
1653 return d->legends;
1654}
1655
1657{
1658 const QPoint pos = mapFromGlobal(event->globalPos());
1659
1660 Q_FOREACH (AbstractCoordinatePlane *plane, d->coordinatePlanes) {
1661 if (plane->geometry().contains(event->pos()) && plane->diagrams().size() > 0) {
1663 event->button(), event->buttons(), event->modifiers());
1664 plane->mousePressEvent(&ev);
1665 d->mouseClickedPlanes.append(plane);
1666 }
1667 }
1668}
1669
1671{
1672 const QPoint pos = mapFromGlobal(event->globalPos());
1673
1674 Q_FOREACH (AbstractCoordinatePlane *plane, d->coordinatePlanes) {
1675 if (plane->geometry().contains(event->pos()) && plane->diagrams().size() > 0) {
1677 event->button(), event->buttons(), event->modifiers());
1678 plane->mouseDoubleClickEvent(&ev);
1679 }
1680 }
1681}
1682
1684{
1685 auto eventReceivers =
1686#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
1687 QSet<AbstractCoordinatePlane *>(d->mouseClickedPlanes.begin(), d->mouseClickedPlanes.end());
1688#else
1689 QSet<AbstractCoordinatePlane *>::fromList(d->mouseClickedPlanes);
1690#endif
1691
1692 Q_FOREACH (AbstractCoordinatePlane *plane, d->coordinatePlanes) {
1693 if (plane->geometry().contains(event->pos()) && plane->diagrams().size() > 0) {
1694 eventReceivers.insert(plane);
1695 }
1696 }
1697
1698 const QPoint pos = mapFromGlobal(event->globalPos());
1699
1701 QMouseEvent ev(QEvent::MouseMove, pos, event->globalPos(),
1702 event->button(), event->buttons(), event->modifiers());
1703 plane->mouseMoveEvent(&ev);
1704 }
1705}
1706
1708{
1709 auto eventReceivers =
1710#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
1711 QSet<AbstractCoordinatePlane *>(d->mouseClickedPlanes.begin(), d->mouseClickedPlanes.end());
1712#else
1713 QSet<AbstractCoordinatePlane *>::fromList(d->mouseClickedPlanes);
1714#endif
1715
1716 Q_FOREACH (AbstractCoordinatePlane *plane, d->coordinatePlanes) {
1717 if (plane->geometry().contains(event->pos()) && plane->diagrams().size() > 0) {
1718 eventReceivers.insert(plane);
1719 }
1720 }
1721
1722 const QPoint pos = mapFromGlobal(event->globalPos());
1723
1726 event->button(), event->buttons(), event->modifiers());
1727 plane->mouseReleaseEvent(&ev);
1728 }
1729
1730 d->mouseClickedPlanes.clear();
1731}
1732
1734{
1735 if (event->type() == QEvent::ToolTip) {
1736 const QHelpEvent *const helpEvent = static_cast<QHelpEvent *>(event);
1737 for (int stage = 0; stage < 2; ++stage) {
1738 Q_FOREACH (const AbstractCoordinatePlane *const plane, d->coordinatePlanes) {
1739 Q_FOREACH (const AbstractDiagram *diagram, plane->diagrams()) {
1740
1741 QModelIndex index;
1742 if (stage == 0) {
1743 // First search at the exact position
1744 index = diagram->indexAt(helpEvent->pos());
1745 } else {
1746 // Second, search in a larger area, which is easier to hit on screens with higher DPI.
1747 const QModelIndexList indexes = diagram->indexesIn(QRect(helpEvent->pos() - QPoint(15, 15), QSize(30, 30)));
1748 index = indexes.isEmpty() ? QModelIndex() : indexes.front();
1749 }
1750
1751 const QVariant toolTip = index.data(Qt::ToolTipRole);
1752 if (toolTip.isValid()) {
1753 const QPoint pos = mapFromGlobal(helpEvent->pos());
1754 const QRect rect(pos - QPoint(1, 1), QSize(3, 3));
1755 QToolTip::showText(QCursor::pos(), toolTip.toString(), this, rect);
1756 return true;
1757 }
1758 }
1759 }
1760 }
1761 }
1762 return QWidget::event(event);
1763}
1764
1766{
1767 return d_func()->useNewLayoutSystem;
1768}
1770{
1771 if (d_func()->useNewLayoutSystem != value)
1772 d_func()->useNewLayoutSystem = value;
1773}
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.
virtual void paintIntoRect(QPainter &painter, const QRect &rect)
Draws the background and frame, then calls paint().
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
virtual void mousePressEvent(QMouseEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)
void setReferenceCoordinatePlane(AbstractCoordinatePlane *plane)
virtual void mouseDoubleClickEvent(QMouseEvent *event)
AbstractCoordinatePlane * referenceCoordinatePlane() const
virtual void mouseMoveEvent(QMouseEvent *event)
AbstractDiagram defines the interface for diagram classes.
QModelIndexList indexesIn(const QRect &rect) const
QModelIndex indexAt(const QPoint &point) const override
void setParentLayout(QLayout *lay)
virtual void paintAll(QPainter &painter)
virtual bool isAbscissa() const
virtual Position position() const
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)
FrameAttributes frameAttributes() const
void setGlobalLeadingRight(int leading)
void mouseDoubleClickEvent(QMouseEvent *event) override
LegendList legends()
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.
Legend defines the interface for the legend drawing class.
const RelativePosition floatingPosition() const
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.
void paintAll(QPainter &painter) override
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
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)
bool contains(const QRect &rectangle, bool proper) const const
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 Fri Feb 23 2024 00:02:58 for KD Chart API Documentation by doxygen 1.9.8