12 #include "KDChartChart_p.h"
14 #include <QApplication>
16 #include <QGridLayout>
19 #include <QLayoutItem>
21 #include <QPaintEvent>
23 #include <QPushButton>
33 #include "KDChartPainterSaver_p.h"
41 #include "../evaldialog/evaldialog.h"
44 #include <KDABLibFakes>
54 static bool isZeroArea(
const QRect &r)
56 return !r.width() || !r.height();
59 static QString lineProlog(
int nestingDepth,
int lineno)
61 QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(
':')));
62 QString indent(nestingDepth * 4, QLatin1Char(
' '));
63 return numbering + indent;
66 static void dumpLayoutTreeRecurse(QLayout *l,
int *counter,
int depth)
68 const QLatin1String colorOn(isZeroArea(l->geometry()) ?
"\033[0m" :
"\033[32m");
69 const QLatin1String colorOff(
"\033[0m");
71 QString prolog = lineProlog(depth, *counter);
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()
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);
87 if (!isZeroArea(child->geometry())) {
88 prolog = lineProlog(depth + 1, *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()
101 static void dumpLayoutTree(QLayout *l)
104 dumpLayoutTreeRecurse(l, &counter, 0);
109 {Qt::AlignTop | Qt::AlignLeft, Qt::AlignTop | Qt::AlignHCenter, Qt::AlignTop | Qt::AlignRight},
110 {Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight},
111 {Qt::AlignBottom | Qt::AlignLeft, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignBottom | Qt::AlignRight}};
163 class MyWidgetItem :
public QWidgetItem
166 explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = {})
169 setAlignment(alignment);
179 QSize sizeHint()
const override
181 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
182 return w->sizeHint();
185 QSize minimumSize()
const override
187 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
188 return w->minimumSize();
191 QSize maximumSize()
const override
193 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
194 return w->maximumSize();
197 Qt::Orientations expandingDirections()
const override
199 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
203 Qt::Orientations e = w->sizePolicy().expandingDirections();
207 void setGeometry(
const QRect &g)
override
209 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
213 QRect geometry()
const override
215 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
216 return w->geometry();
219 bool hasHeightForWidth()
const override
221 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
222 bool ret = !isEmpty() && qobject_cast<Legend *>(w)->hasHeightForWidth();
226 int heightForWidth(
int width)
const override
228 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
229 int ret = w->heightForWidth(width);
233 bool isEmpty()
const override
235 QWidget *w =
const_cast<MyWidgetItem *
>(
this)->widget();
239 return w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide);
250 QLayout *layout = item->layout();
252 const int count = layout->count();
253 for (
int i = 0; i < count; i++) {
260 void Chart::Private::slotUnregisterDestroyedLegend(
Legend *l)
262 chart->takeLegend(l);
265 void Chart::Private::slotUnregisterDestroyedHeaderFooter(
HeaderFooter *hf)
267 chart->takeHeaderFooter(hf);
272 coordinatePlanes.removeAll(plane);
274 if (p->referenceCoordinatePlane() == plane) {
275 p->setReferenceCoordinatePlane(
nullptr);
281 Chart::Private::Private(
Chart *chart_)
283 , useNewLayoutSystem(false)
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)
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;
310 Chart::Private::~Private()
319 struct ConnectedComponentsComparator
321 bool operator()(
const LayoutGraphNode *lhs,
const LayoutGraphNode *rhs)
const
323 return lhs->priority < rhs->priority;
329 QVector<LayoutGraphNode *> connectedComponents;
330 QHash<LayoutGraphNode *, VisitorState> visitedComponents;
331 for (LayoutGraphNode *node : qAsConst(nodeList)) {
332 visitedComponents[node] =
Unknown;
335 for (
int i = 0; i < nodeList.size(); ++i) {
336 LayoutGraphNode *curNode = nodeList[i];
337 LayoutGraphNode *representativeNode = curNode;
338 if (visitedComponents[curNode] !=
Visited) {
339 QStack<LayoutGraphNode *> stack;
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;
354 connectedComponents.append(representativeNode);
357 std::sort(connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator());
358 return connectedComponents;
361 struct PriorityComparator
364 PriorityComparator(QHash<AbstractCoordinatePlane *, LayoutGraphNode *> mapping)
370 const LayoutGraphNode *lhsNode = m_mapping[lhs];
372 const LayoutGraphNode *rhsNode = m_mapping[rhs];
374 return lhsNode->priority < rhsNode->priority;
377 const QHash<AbstractCoordinatePlane *, LayoutGraphNode *> m_mapping;
382 if (node && node->diagramPlane && node->diagramPlane->diagram()) {
383 auto *diag = qobject_cast<AbstractCartesianDiagram *>(node->diagramPlane->diagram());
385 const auto constAxes = diag->axes();
387 switch (axis->position()) {
388 case (CartesianAxis::Top):
389 node->topAxesLayout =
true;
391 case (CartesianAxis::Bottom):
392 node->bottomAxesLayout =
true;
394 case (CartesianAxis::Left):
395 node->leftAxesLayout =
true;
397 case (CartesianAxis::Right):
398 node->rightAxesLayout =
true;
408 lhs->topAxesLayout |= rhs->topAxesLayout;
409 rhs->topAxesLayout = lhs->topAxesLayout;
411 lhs->bottomAxesLayout |= rhs->bottomAxesLayout;
412 rhs->bottomAxesLayout = lhs->bottomAxesLayout;
414 lhs->leftAxesLayout |= rhs->leftAxesLayout;
415 rhs->leftAxesLayout = lhs->leftAxesLayout;
417 lhs->rightAxesLayout |= rhs->rightAxesLayout;
418 rhs->rightAxesLayout = lhs->rightAxesLayout;
423 Chart::Private::AxisType type,
424 QVector<CartesianAxis *> *sharedAxes)
426 if (!plane || !plane->
diagram())
431 auto *diagram = qobject_cast<AbstractCartesianDiagram *>(plane->
diagram());
435 QList<CartesianAxis *> axes;
437 const auto constAxes = diagram->axes();
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))) {
448 qobject_cast<AbstractCartesianDiagram *>(curPlane->diagram());
452 const auto constAxes = diagram->axes();
454 if (curSearchedAxis == curAxis) {
455 result.append(curPlane);
456 if (!sharedAxes->contains(curSearchedAxis))
457 sharedAxes->append(curSearchedAxis);
472 QVector<LayoutGraphNode *> Chart::Private::buildPlaneLayoutGraph()
474 QHash<AbstractCoordinatePlane *, LayoutGraphNode *> planeNodeMapping;
475 QVector<LayoutGraphNode *> allNodes;
478 if (curPlane->diagram()) {
479 allNodes.append(
new LayoutGraphNode);
480 allNodes[allNodes.size() - 1]->diagramPlane = curPlane;
481 allNodes[allNodes.size() - 1]->priority = allNodes.size();
483 planeNodeMapping[curPlane] = allNodes[allNodes.size() - 1];
487 for (LayoutGraphNode *curNode : qAsConst(allNodes)) {
488 QVector<CartesianAxis *> sharedAxes;
490 Q_ASSERT(sharedAxes.size() < 2);
492 if (sharedAxes.size() == 1 && xSharedPlanes.size() > 1) {
495 for (
int i = 0; i < xSharedPlanes.size() - 1; ++i) {
496 LayoutGraphNode *tmpNode = planeNodeMapping[xSharedPlanes[i]];
498 LayoutGraphNode *tmpNode2 = planeNodeMapping[xSharedPlanes[i + 1]];
500 tmpNode->bottomSuccesor = tmpNode2;
512 LayoutGraphNode axisInfoNode;
513 for (
int count = 0; count < 2; ++count) {
514 for (
int i = 0; i < xSharedPlanes.size(); ++i) {
521 Q_ASSERT(sharedAxes.size() < 2);
522 if (sharedAxes.size() == 1 && ySharedPlanes.size() > 1) {
525 for (
int i = 0; i < ySharedPlanes.size() - 1; ++i) {
526 LayoutGraphNode *tmpNode = planeNodeMapping[ySharedPlanes[i]];
528 LayoutGraphNode *tmpNode2 = planeNodeMapping[ySharedPlanes[i + 1]];
530 tmpNode->leftSuccesor = tmpNode2;
542 LayoutGraphNode axisInfoNode;
543 for (
int count = 0; count < 2; ++count) {
544 for (
int i = 0; i < ySharedPlanes.size(); ++i) {
550 if (curNode->diagramPlane->referenceCoordinatePlane())
551 curNode->sharedSuccesor = planeNodeMapping[curNode->diagramPlane->referenceCoordinatePlane()];
557 QHash<AbstractCoordinatePlane *, PlaneInfo> Chart::Private::buildPlaneLayoutInfos()
571 QHash<CartesianAxis *, AxisInfo> axisInfos;
572 QHash<AbstractCoordinatePlane *, PlaneInfo> planeInfos;
577 planeInfos.insert(plane, p);
579 const auto constDiagrams = plane->
diagrams();
582 qobject_cast<AbstractCartesianDiagram *>(abstractDiagram);
587 const auto constAxes = diagram->axes();
589 if (!axisInfos.contains(axis)) {
596 axisInfos.insert(axis, i);
598 AxisInfo i = axisInfos[axis];
599 if (i.plane == plane) {
606 PlaneInfo pi = planeInfos[plane];
608 if (!pi.referencePlane) {
610 pi.referencePlane = i.plane;
611 if (axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right) {
612 pi.horizontalOffset += 1;
614 planeInfos[plane] = pi;
616 pi = planeInfos[i.plane];
617 if (axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom) {
618 pi.verticalOffset += 1;
621 planeInfos[i.plane] = pi;
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;
637 void Chart::Private::slotLayoutPlanes()
640 const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
641 : QBoxLayout::TopToBottom;
642 if (planesLayout && dataAndLegendLayout)
643 dataAndLegendLayout->removeItem(planesLayout);
645 const bool hadPlanesLayout = planesLayout !=
nullptr;
646 int left, top, right, bottom;
648 planesLayout->getContentsMargins(&left, &top, &right, &bottom);
659 planeLayoutItems.clear();
663 planesLayout =
new QBoxLayout(oldPlanesDirection);
665 isPlanesLayoutDirty =
true;
667 if (useNewLayoutSystem) {
668 gridPlaneLayout =
new QGridLayout;
669 planesLayout->addLayout(gridPlaneLayout);
672 planesLayout->setContentsMargins(left, top, right, bottom);
673 planesLayout->setObjectName(QString::fromLatin1(
"planesLayout"));
679 QVector<LayoutGraphNode *> vals = buildPlaneLayoutGraph();
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) {
690 for (LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor) {
691 Q_ASSERT(curColComponent->diagramPlane->diagrams().size() == 1);
692 const auto constDiagrams = curColComponent->diagramPlane->diagrams();
694 const int planeRowOffset = 1;
695 const int planeColOffset = 1;
699 planeLayoutItems << curColComponent->diagramPlane;
700 auto *cartDiag = qobject_cast<AbstractCartesianDiagram *>(diagram);
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;
713 const auto constAxes = cartDiag->axes();
715 if (axis->isAbscissa()) {
716 if (curColComponent->bottomSuccesor)
719 if (laidOutAxes.contains(axis))
723 switch (axis->position()) {
724 case (CartesianAxis::Top):
726 topLayout =
new QVBoxLayout;
727 topLayout->addItem(axis);
728 axis->setParentLayout(topLayout);
730 case (CartesianAxis::Bottom):
732 bottomLayout =
new QVBoxLayout;
733 bottomLayout->addItem(axis);
734 axis->setParentLayout(bottomLayout);
736 case (CartesianAxis::Left):
738 leftLayout =
new QHBoxLayout;
739 leftLayout->addItem(axis);
740 axis->setParentLayout(leftLayout);
742 case (CartesianAxis::Right):
744 rightLayout =
new QHBoxLayout;
746 rightLayout->addItem(axis);
747 axis->setParentLayout(rightLayout);
750 planeLayoutItems << axis;
751 laidOutAxes.insert(axis);
754 gridPlaneLayout->addLayout(leftLayout, row + planeRowOffset, col, 2, 1,
755 Qt::AlignRight | Qt::AlignVCenter);
757 gridPlaneLayout->addLayout(rightLayout, row, col + planeColOffset + 2, 2, 1,
758 Qt::AlignLeft | Qt::AlignVCenter);
760 gridPlaneLayout->addLayout(topLayout, row, col + planeColOffset, 1, 2,
761 Qt::AlignBottom | Qt::AlignHCenter);
763 gridPlaneLayout->addLayout(bottomLayout, row + planeRowOffset + 2,
764 col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter);
766 gridPlaneLayout->addItem(curColComponent->diagramPlane, row, col, 4, 4);
767 curColComponent->diagramPlane->setParentLayout(gridPlaneLayout);
769 col += planeColOffset + 2 + (1);
774 const int rowOffset = axisOffset + 2;
786 if (dataAndLegendLayout) {
787 dataAndLegendLayout->addLayout(planesLayout, 1, 1);
788 dataAndLegendLayout->setRowStretch(1, 1000);
789 dataAndLegendLayout->setColumnStretch(1, 1000);
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();
798 qDebug() << Q_FUNC_INFO <<
"item at" << i << j <<
"no item present";
804 if (hadPlanesLayout) {
805 planesLayout->setContentsMargins(left, top, right, bottom);
808 planesLayout->setContentsMargins(0, 0, 0, 0);
809 planesLayout->setSpacing(0);
810 planesLayout->setObjectName(QString::fromLatin1(
"planesLayout"));
815 QHash<AbstractCoordinatePlane *, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
816 QHash<AbstractAxis *, AxisInfo> axisInfos;
818 Q_ASSERT(planeInfos.contains(plane));
819 PlaneInfo &pi = planeInfos[plane];
820 const int column = pi.horizontalOffset;
821 const int row = pi.verticalOffset;
823 QGridLayout *planeLayout = pi.gridLayout;
826 PlaneInfo &refPi = pi;
829 while (!planeLayout && refPi.referencePlane) {
830 refPi = planeInfos[refPi.referencePlane];
831 planeLayout = refPi.gridLayout;
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.");
837 planesLayout->addLayout(planeLayout);
843 planeLayoutItems << plane;
845 planeLayout->addItem(plane, row, column, 1, 1, {});
847 planeLayout->setRowStretch(row, 2);
848 planeLayout->setColumnStretch(column, 2);
849 const auto constDiagrams = plane->diagrams();
852 qobject_cast<AbstractCartesianDiagram *>(abstractDiagram);
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;
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"));
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"));
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"));
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"));
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;
894 const auto constAxes = diagram->axes();
896 if (axisInfos.contains(axis)) {
900 axis->setCachedSizeDirty();
902 planeLayoutItems << axis;
904 switch (axis->position()) {
905 case CartesianAxis::Top:
906 axis->setParentLayout(pi.topAxesLayout);
907 pi.topAxesLayout->addItem(axis);
909 case CartesianAxis::Bottom:
910 axis->setParentLayout(pi.bottomAxesLayout);
911 pi.bottomAxesLayout->addItem(axis);
913 case CartesianAxis::Left:
914 axis->setParentLayout(pi.leftAxesLayout);
915 pi.leftAxesLayout->addItem(axis);
917 case CartesianAxis::Right:
918 axis->setParentLayout(pi.rightAxesLayout);
919 pi.rightAxesLayout->addItem(axis);
922 Q_ASSERT_X(
false,
"Chart::paintEvent",
"unknown axis position");
925 axisInfos.insert(axis, AxisInfo());
932 if (!pi.topAxesLayout->parent()) {
933 planeLayout->addLayout(pi.topAxesLayout, row - 1, column);
935 if (!pi.bottomAxesLayout->parent()) {
936 planeLayout->addLayout(pi.bottomAxesLayout, row + 1, column);
938 if (!pi.leftAxesLayout->parent()) {
939 planeLayout->addLayout(pi.leftAxesLayout, row, column - 1);
941 if (!pi.rightAxesLayout->parent()) {
942 planeLayout->addLayout(pi.rightAxesLayout, row, column + 1);
947 #define ADD_AUTO_SPACER_IF_NEEDED( \
948 spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout) \
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; \
958 if (plane->isCornerSpacersEnabled()) {
966 if (dataAndLegendLayout) {
967 dataAndLegendLayout->addLayout(planesLayout, 1, 1);
968 dataAndLegendLayout->setRowStretch(1, 1000);
969 dataAndLegendLayout->setColumnStretch(1, 1000);
976 void Chart::Private::createLayouts()
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();
987 vLayout =
new QVBoxLayout();
988 vLayout->setContentsMargins(0, 0, 0, 0);
989 vLayout->setObjectName(QString::fromLatin1(
"vLayout"));
991 layout->addLayout(vLayout, 1000);
992 layout->addSpacing(globalLeadingRight);
993 rightOuterSpacer = layout->itemAt(layout->count() - 1)->spacerItem();
996 vLayout->addSpacing(globalLeadingTop);
997 topOuterSpacer = vLayout->itemAt(vLayout->count() - 1)->spacerItem();
999 headerLayout =
new QGridLayout();
1000 headerLayout->setContentsMargins(0, 0, 0, 0);
1001 vLayout->addLayout(headerLayout);
1003 dataAndLegendLayout =
new QGridLayout();
1004 dataAndLegendLayout->setContentsMargins(0, 0, 0, 0);
1005 dataAndLegendLayout->setObjectName(QString::fromLatin1(
"dataAndLegendLayout"));
1006 vLayout->addLayout(dataAndLegendLayout, 1000);
1008 footerLayout =
new QGridLayout();
1009 footerLayout->setContentsMargins(0, 0, 0, 0);
1010 footerLayout->setObjectName(QString::fromLatin1(
"footerLayout"));
1011 vLayout->addLayout(footerLayout);
1017 for (
int row = 0; row < 3; ++row) {
1018 for (
int column = 0; column < 3; ++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;
1026 QGridLayout *outerLayout = headOrFoot == 0 ? headerLayout : footerLayout;
1027 outerLayout->addLayout(innerLayout, row, column, align);
1033 vLayout->addSpacing(globalLeadingBottom);
1034 bottomOuterSpacer = vLayout->itemAt(vLayout->count() - 1)->spacerItem();
1037 dataAndLegendLayout->addLayout(planesLayout, 1, 1);
1038 dataAndLegendLayout->setRowStretch(1, 1);
1039 dataAndLegendLayout->setColumnStretch(1, 1);
1042 void Chart::Private::slotResizePlanes()
1044 if (!dataAndLegendLayout) {
1047 if (!overrideSize.isValid()) {
1056 plane->layoutDiagrams();
1060 void Chart::Private::updateDirtyLayouts()
1062 if (isPlanesLayoutDirty) {
1064 p->setGridNeedsRecalculate();
1066 p->layoutDiagrams();
1069 if (isPlanesLayoutDirty || isFloatingLegendsLayoutDirty) {
1070 chart->reLayoutFloatingLegends();
1072 isPlanesLayoutDirty =
false;
1073 isFloatingLegendsLayoutDirty =
false;
1076 void Chart::Private::reapplyInternalLayouts()
1078 QRect geo = layout->geometry();
1081 layout->setGeometry(geo);
1085 void Chart::Private::paintAll(QPainter *painter)
1087 updateDirtyLayouts();
1089 QRect rect(QPoint(0, 0), overrideSize.isValid() ? overrideSize : chart->size());
1094 AbstractAreaBase::paintBackgroundAttributes(*painter, rect, backgroundAttributes);
1096 AbstractAreaBase::paintFrameAttributes(*painter, rect, frameAttributes);
1098 chart->reLayoutFloatingLegends();
1101 planeLayoutItem->paintAll(*painter);
1103 for (
TextArea *textLayoutItem : qAsConst(textLayoutItems)) {
1104 textLayoutItem->paintAll(*painter);
1106 for (
Legend *legend : qAsConst(legends)) {
1107 const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
1110 legend->paintIntoRect(*painter, legend->geometry());
1119 Chart::Chart(QWidget *parent)
1121 , _d(new Private(this))
1123 #if defined KDAB_EVAL
1124 EvalDialog::checkEvalLicense(
"KD Chart");
1130 frameAttrs.
setPen(QPen(Qt::black));
1146 d->frameAttributes = a;
1151 return d->frameAttributes;
1156 d->backgroundAttributes = a;
1161 return d->backgroundAttributes;
1167 if (layout ==
d->planesLayout)
1169 if (
d->planesLayout) {
1172 for (
int i =
d->planesLayout->count() - 1; i >= 0; --i) {
1173 d->planesLayout->takeAt(i);
1175 delete d->planesLayout;
1177 d->planesLayout = qobject_cast<QBoxLayout *>(layout);
1178 d->slotLayoutPlanes();
1183 return d->planesLayout;
1188 if (
d->coordinatePlanes.isEmpty()) {
1189 qWarning() <<
"Chart::coordinatePlane: warning: no coordinate plane defined.";
1192 return d->coordinatePlanes.first();
1198 return d->coordinatePlanes;
1209 if (index < 0 || index >
d->coordinatePlanes.count()) {
1214 d, &Private::slotUnregisterDestroyedPlane);
1219 d->coordinatePlanes.insert(index, plane);
1221 d->slotLayoutPlanes();
1227 if (plane && oldPlane_ != plane) {
1229 if (
d->coordinatePlanes.count()) {
1231 oldPlane =
d->coordinatePlanes.first();
1232 if (oldPlane == plane)
1244 const int idx =
d->coordinatePlanes.indexOf(plane);
1246 d->coordinatePlanes.takeAt(idx);
1247 disconnect(plane,
nullptr,
d,
nullptr);
1248 disconnect(plane,
nullptr,
this,
nullptr);
1251 d->mouseClickedPlanes.removeAll(plane);
1253 d->slotLayoutPlanes();
1269 d->globalLeadingLeft = leading;
1270 d->leftOuterSpacer->changeSize(leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
1271 d->reapplyInternalLayouts();
1276 return d->globalLeadingLeft;
1281 d->globalLeadingTop = leading;
1282 d->topOuterSpacer->changeSize(0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed);
1283 d->reapplyInternalLayouts();
1288 return d->globalLeadingTop;
1293 d->globalLeadingRight = leading;
1294 d->rightOuterSpacer->changeSize(leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
1295 d->reapplyInternalLayouts();
1300 return d->globalLeadingRight;
1305 d->globalLeadingBottom = leading;
1306 d->bottomOuterSpacer->changeSize(0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed);
1307 d->reapplyInternalLayouts();
1312 return d->globalLeadingBottom;
1317 if (target.isEmpty() || !painter) {
1325 if (
dynamic_cast<QWidget *
>(painter->device()) !=
nullptr) {
1327 qreal(target.height()) / qreal(geometry().size().height()));
1332 const qreal resX = qreal(logicalDpiX()) / qreal(painter->device()->logicalDpiX());
1333 const qreal resY = qreal(logicalDpiY()) / qreal(painter->device()->logicalDpiY());
1336 qreal(target.height()) / qreal(geometry().size().height()) * resY);
1339 const QPoint translation = target.topLeft();
1340 painter->translate(translation);
1345 const bool differentSize = target.size() != size();
1347 if (differentSize) {
1348 oldGeometry = geometry();
1349 d->isPlanesLayoutDirty =
true;
1350 d->isFloatingLegendsLayoutDirty =
true;
1352 d->dataAndLegendLayout->setGeometry(QRect(QPoint(), target.size()));
1355 d->overrideSize = target.size();
1356 d->paintAll(painter);
1357 d->overrideSize = QSize();
1359 if (differentSize) {
1361 d->dataAndLegendLayout->setGeometry(oldGeometry);
1362 d->isPlanesLayoutDirty =
true;
1363 d->isFloatingLegendsLayoutDirty =
true;
1370 painter->translate(-translation.x(), -translation.y());
1379 d->isPlanesLayoutDirty =
true;
1380 d->isFloatingLegendsLayoutDirty =
true;
1381 QWidget::resizeEvent(
event);
1387 const bool hidden =
legend->isHidden() &&
legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
1391 legend->setGeometry(QRect(
legend->geometry().topLeft(), legendSize));
1397 const Qt::Alignment alignTopLeft = Qt::AlignTop | Qt::AlignLeft;
1398 if ((relPos.
alignment() & alignTopLeft) != alignTopLeft) {
1399 if (relPos.
alignment() & Qt::AlignRight)
1400 pt.rx() -= legendSize.width();
1401 else if (relPos.
alignment() & Qt::AlignHCenter)
1402 pt.rx() -= 0.5 * legendSize.width();
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();
1410 legend->move(
static_cast<int>(pt.x()),
static_cast<int>(pt.y()));
1417 QPainter painter(
this);
1418 d->paintAll(&painter);
1429 qWarning(
"Unknown header/footer position");
1433 d->headerFooters.append(hf);
1434 d->textLayoutItems.append(hf);
1436 d, &Private::slotUnregisterDestroyedHeaderFooter);
1438 d, &Private::slotHeaderFooterPositionChanged);
1452 QVBoxLayout *headerFooterLayout =
d->innerHdFtLayouts[innerLayoutIdx][row][column];
1456 headerFooterLayout->addItem(hf);
1458 d->slotResizePlanes();
1466 if (
d->headerFooters.count()) {
1467 if (!oldHeaderFooter) {
1468 oldHeaderFooter =
d->headerFooters.first();
1474 delete oldHeaderFooter;
1486 d, &Private::slotUnregisterDestroyedHeaderFooter);
1488 d->headerFooters.takeAt(idx);
1491 d->textLayoutItems.remove(
d->textLayoutItems.indexOf(
headerFooter));
1493 d->slotResizePlanes();
1496 void Chart::Private::slotHeaderFooterPositionChanged(
HeaderFooter *hf)
1498 chart->takeHeaderFooter(hf);
1499 chart->addHeaderFooter(hf);
1504 if (
d->headerFooters.isEmpty()) {
1507 return d->headerFooters.first();
1513 return d->headerFooters;
1518 auto *legend = qobject_cast<Legend *>(aw);
1520 chart->takeLegend(legend);
1521 chart->addLegendInternal(legend,
false);
1527 addLegendInternal(
legend,
true);
1531 void Chart::addLegendInternal(
Legend *legend,
bool setMeasures)
1539 qWarning(
"Not showing legend because PositionCenter is not supported for legends.");
1546 qWarning(
"Not showing legend because of unknown legend position.");
1557 Measure measure(textAttrs.fontSize());
1559 measure.setValue(20);
1560 textAttrs.setFontSize(measure);
1565 measure.setValue(24);
1580 QLayoutItem *edgeItem =
d->dataAndLegendLayout->itemAtPosition(row, column);
1581 auto *alignmentsLayout =
dynamic_cast<QGridLayout *
>(edgeItem);
1582 Q_ASSERT(!edgeItem || alignmentsLayout);
1583 if (!alignmentsLayout) {
1584 alignmentsLayout =
new QGridLayout;
1585 d->dataAndLegendLayout->addLayout(alignmentsLayout, row, column);
1586 alignmentsLayout->setContentsMargins(0, 0, 0, 0);
1594 for (
int i = 0; i < 3; i++) {
1595 for (
int j = 0; j < 3; j++) {
1605 QLayoutItem *alignmentItem = alignmentsLayout->itemAtPosition(row, column);
1606 auto *sameAlignmentLayout =
dynamic_cast<QVBoxLayout *
>(alignmentItem);
1607 Q_ASSERT(!alignmentItem || sameAlignmentLayout);
1608 if (!sameAlignmentLayout) {
1609 sameAlignmentLayout =
new QVBoxLayout;
1610 alignmentsLayout->addLayout(sameAlignmentLayout, row, column);
1611 sameAlignmentLayout->setContentsMargins(0, 0, 0, 0);
1618 d, &Private::slotUnregisterDestroyedLegend);
1620 d, &Private::slotLegendPositionChanged);
1623 d->slotResizePlanes();
1629 Legend *oldLegend = oldLegend_;
1630 if (
d->legends.count()) {
1632 oldLegend =
d->legends.first();
1645 const int idx =
d->legends.indexOf(
legend);
1650 d->legends.takeAt(idx);
1651 disconnect(
legend,
nullptr,
d,
nullptr);
1652 disconnect(
legend,
nullptr,
this,
nullptr);
1654 legend->setParent(
nullptr);
1656 d->slotResizePlanes();
1662 return d->legends.isEmpty() ? 0 :
d->legends.first();
1672 const QPoint pos = mapFromGlobal(
event->globalPos());
1675 if (plane->geometry().contains(
event->pos()) && plane->diagrams().size() > 0) {
1676 QMouseEvent ev(QEvent::MouseButtonPress, pos,
event->globalPos(),
1678 plane->mousePressEvent(&ev);
1679 d->mouseClickedPlanes.append(plane);
1686 const QPoint pos = mapFromGlobal(
event->globalPos());
1689 if (plane->geometry().contains(
event->pos()) && plane->diagrams().size() > 0) {
1690 QMouseEvent ev(QEvent::MouseButtonPress, pos,
event->globalPos(),
1692 plane->mouseDoubleClickEvent(&ev);
1699 auto eventReceivers =
1700 #if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
1701 QSet<AbstractCoordinatePlane *>(
d->mouseClickedPlanes.begin(),
d->mouseClickedPlanes.end());
1703 QSet<AbstractCoordinatePlane *>::fromList(
d->mouseClickedPlanes);
1707 if (plane->geometry().contains(
event->pos()) && plane->diagrams().size() > 0) {
1708 eventReceivers.insert(plane);
1712 const QPoint pos = mapFromGlobal(
event->globalPos());
1715 QMouseEvent ev(QEvent::MouseMove, pos,
event->globalPos(),
1717 plane->mouseMoveEvent(&ev);
1723 auto eventReceivers =
1724 #if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
1725 QSet<AbstractCoordinatePlane *>(
d->mouseClickedPlanes.begin(),
d->mouseClickedPlanes.end());
1727 QSet<AbstractCoordinatePlane *>::fromList(
d->mouseClickedPlanes);
1731 if (plane->geometry().contains(
event->pos()) && plane->diagrams().size() > 0) {
1732 eventReceivers.insert(plane);
1736 const QPoint pos = mapFromGlobal(
event->globalPos());
1739 QMouseEvent ev(QEvent::MouseButtonRelease, pos,
event->globalPos(),
1741 plane->mouseReleaseEvent(&ev);
1744 d->mouseClickedPlanes.clear();
1749 if (
event->type() == QEvent::ToolTip) {
1750 const QHelpEvent *
const helpEvent =
static_cast<QHelpEvent *
>(
event);
1751 for (
int stage = 0; stage < 2; ++stage) {
1753 const auto constDiagrams = plane->diagrams();
1759 index = diagram->indexAt(helpEvent->pos());
1762 const QModelIndexList indexes = diagram->indexesIn(QRect(helpEvent->pos() - QPoint(15, 15), QSize(30, 30)));
1763 index = indexes.isEmpty() ? QModelIndex() : indexes.front();
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);
1777 return QWidget::event(
event);
1782 return d_func()->useNewLayoutSystem;
1787 d_func()->useNewLayoutSystem = value;
static const Qt::Alignment s_gridAlignments[3][3]
static void getRowAndColumnForPosition(KDChartEnums::PositionValue pos, int *row, int *column)
static void mergeNodeAxisInformation(LayoutGraphNode *lhs, LayoutGraphNode *rhs)
static void invalidateLayoutTree(QLayoutItem *item)
static QVector< LayoutGraphNode * > getPrioritySortedConnectedComponents(QVector< LayoutGraphNode * > &nodeList)
void checkExistingAxes(LayoutGraphNode *node)
static CoordinatePlaneList findSharingAxisDiagrams(AbstractCoordinatePlane *plane, const CoordinatePlaneList &list, Chart::Private::AxisType type, QVector< CartesianAxis * > *sharedAxes)
#define ADD_AUTO_SPACER_IF_NEEDED( spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout)
Definition of global enums.
@ MeasureOrientationMinimum
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
void destroyedCoordinatePlane(AbstractCoordinatePlane *)
AbstractDiagramList diagrams()
AbstractDiagram * diagram()
void setParent(Chart *parent)
AbstractCoordinatePlane * referenceCoordinatePlane() const
AbstractDiagram defines the interface for diagram classes.
void removeFromParentLayout()
void setParentLayout(QLayout *lay)
Cartesian coordinate plane.
A chart with one or more diagrams.
AbstractCoordinatePlane * coordinatePlane()
void reLayoutFloatingLegends()
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)
void takeLegend(Legend *legend)
void takeCoordinatePlane(AbstractCoordinatePlane *plane)
void setCoordinatePlaneLayout(QLayout *layout)
void insertCoordinatePlane(int index, AbstractCoordinatePlane *plane)
HeaderFooterList headerFooters()
void mouseMoveEvent(QMouseEvent *event) override
bool event(QEvent *event) override
void resizeEvent(QResizeEvent *event) override
void setGlobalLeading(int left, int top, int right, int bottom)
void setBackgroundAttributes(const BackgroundAttributes &a)
Specify the background attributes to be used, by default there is no background.
void addHeaderFooter(HeaderFooter *headerFooter)
void mousePressEvent(QMouseEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
void takeHeaderFooter(HeaderFooter *headerFooter)
void setGlobalLeadingBottom(int leading)
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.
void setPen(const QPen &pen)
void setPadding(int padding)
static void setFactors(qreal factorX, qreal factorY)
static GlobalMeasureScaling * instance()
static void resetFactors()
static void setPaintDevice(QPaintDevice *paintDevice)
static QPaintDevice * paintDevice()
Legend defines the interface for the legend drawing class.
const RelativePosition floatingPosition() const
void destroyedLegend(Legend *)
TextAttributes titleTextAttributes() const
void needSizeHint() override
Qt::Alignment alignment() const
QSize sizeHint() const override
void setReferenceArea(const QWidget *area)
void setTextAttributes(const TextAttributes &a)
Position position() const
TextAttributes textAttributes() const
void setTitleTextAttributes(const TextAttributes &a)
Measure is used to specify relative and absolute sizes in KDChart, e.g. font sizes.
void setRelativeMode(const QObject *area, KDChartEnums::MeasureOrientation orientation)
KDChartEnums::PositionValue value() const
static void setScaleFactor(const qreal scaleFactor)
static void resetScaleFactor()
Defines relative position information: reference area, position in this area (reference position),...
Qt::Alignment alignment() const
const QPointF calculatedPoint(const QSizeF &autoSize) const
Calculate a point, according to the reference area/position and the padding.
A text area in the chart with a background, a frame, etc.
A set of text attributes.
void setFontSize(const Measure &measure)
void setTextAttributes(const TextAttributes &a)
Use this to specify the text attributes to be used for this item.
TextAttributes textAttributes() const
QList< AbstractCoordinatePlane * > CoordinatePlaneList
QList< HeaderFooter * > HeaderFooterList
QList< Legend * > LegendList