KD Chart API Documentation 3.1
Loading...
Searching...
No Matches
KDChartLegend.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 "KDChartLegend.h"
12#include "KDChartLayoutItems.h"
13#include "KDChartLegend_p.h"
14#include "KDTextDocument.h"
18#include <KDChartPalette.h>
20
21#include <QAbstractTextDocumentLayout>
22#include <QFont>
23#include <QGridLayout>
24#include <QLabel>
25#include <QPainter>
26#include <QTextCharFormat>
27#include <QTextCursor>
28#include <QTextDocumentFragment>
29#include <QTextTableCell>
30#include <QTimer>
31#include <QtDebug>
32
33#include <KDABLibFakes>
34
35using namespace KDChart;
36
37Legend::Private::Private()
38 : position(Position::East)
39 , alignment(Qt::AlignCenter)
40 , textAlignment(Qt::AlignCenter)
41 , relativePosition(RelativePosition())
42 , titleText(QObject::tr("Legend"))
43{
44 // By default we specify a simple, hard point as the 'relative' position's ref. point,
45 // since we can not be sure that there will be any parent specified for the legend.
46 relativePosition.setReferencePoints(PositionPoints(QPointF(0.0, 0.0)));
47 relativePosition.setReferencePosition(Position::NorthWest);
48 relativePosition.setAlignment(Qt::AlignTop | Qt::AlignLeft);
49 relativePosition.setHorizontalPadding(Measure(4.0, KDChartEnums::MeasureCalculationModeAbsolute));
50 relativePosition.setVerticalPadding(Measure(4.0, KDChartEnums::MeasureCalculationModeAbsolute));
51}
52
53Legend::Private::~Private()
54{
55 // this block left empty intentionally
56}
57
58#define d d_func()
59
61 : AbstractAreaWidget(new Private(), parent)
62{
63 d->referenceArea = parent;
64 init();
65}
66
68 : AbstractAreaWidget(new Private(), parent)
69{
70 d->referenceArea = parent;
71 init();
73}
74
79
80void Legend::init()
81{
83
84 d->layout = new QGridLayout(this);
85 d->layout->setContentsMargins(2, 2, 2, 2);
86 d->layout->setSpacing(d->spacing);
87
91
94 textAttrs.setFont(QFont(QLatin1String("helvetica"), 10, QFont::Normal, false));
96 textAttrs.setMinimalFontSize(minimalFontSize);
98
101 titleTextAttrs.setFont(QFont(QLatin1String("helvetica"), 12, QFont::Bold, false));
103 titleTextAttrs.setMinimalFontSize(minimalFontSize);
105
108 frameAttrs.setPen(QPen(Qt::black));
109 frameAttrs.setPadding(1);
111
112 d->position = Position::NorthEast;
113 d->alignment = Qt::AlignCenter;
114}
115
117{
118 return sizeHint();
119}
120
121// #define DEBUG_LEGEND_PAINT
122
124{
125#ifdef DEBUG_LEGEND_PAINT
126 qDebug() << "Legend::sizeHint() started";
127#endif
128 for (AbstractLayoutItem *paintItem : qAsConst(d->paintItems)) {
129 paintItem->sizeHint();
130 }
132}
133
135{
136 buildLegend();
137}
138
140{
141#ifdef DEBUG_LEGEND_PAINT
142 qDebug() << "Legend::resizeLayout started";
143#endif
144 if (d->layout) {
145 d->reflowHDatasetItems(this);
146 d->layout->setGeometry(QRect(QPoint(0, 0), size));
147 activateTheLayout();
148 }
149#ifdef DEBUG_LEGEND_PAINT
150 qDebug() << "Legend::resizeLayout done";
151#endif
152}
153
154void Legend::activateTheLayout()
155{
156 if (d->layout && d->layout->parent()) {
157 d->layout->activate();
158 }
159}
160
162{
163 if (d->legendStyle == style) {
164 return;
165 }
166 d->legendStyle = style;
167 setNeedRebuild();
168}
169
171{
172 return d->legendStyle;
173}
174
179{
180 auto *legend = new Legend(new Private(*d), nullptr);
181 legend->setTextAttributes(textAttributes());
182 legend->setTitleTextAttributes(titleTextAttributes());
183 legend->setFrameAttributes(frameAttributes());
184 legend->setUseAutomaticMarkerSize(useAutomaticMarkerSize());
185 legend->setPosition(position());
186 legend->setAlignment(alignment());
187 legend->setTextAlignment(textAlignment());
188 legend->setLegendStyle(legendStyle());
189 return legend;
190}
191
192bool Legend::compare(const Legend *other) const
193{
194 if (other == this) {
195 return true;
196 }
197 if (!other) {
198 return false;
199 }
200
201 return (AbstractAreaBase::compare(other)) && (isVisible() == other->isVisible()) && (position() == other->position()) && (alignment() == other->alignment()) && (textAlignment() == other->textAlignment()) && (floatingPosition() == other->floatingPosition()) && (orientation() == other->orientation()) && (showLines() == other->showLines()) && (texts() == other->texts()) && (brushes() == other->brushes()) && (pens() == other->pens()) && (markerAttributes() == other->markerAttributes()) && (useAutomaticMarkerSize() == other->useAutomaticMarkerSize()) && (textAttributes() == other->textAttributes()) && (titleText() == other->titleText()) && (titleTextAttributes() == other->titleTextAttributes()) && (spacing() == other->spacing()) && (legendStyle() == other->legendStyle());
202}
203
205{
206#ifdef DEBUG_LEGEND_PAINT
207 qDebug() << "entering Legend::paint( QPainter* painter )";
208#endif
209 if (!diagram()) {
210 return;
211 }
212
213 activateTheLayout();
214
215 for (AbstractLayoutItem *paintItem : qAsConst(d->paintItems)) {
216 paintItem->paint(painter);
217 }
218
219#ifdef DEBUG_LEGEND_PAINT
220 qDebug() << "leaving Legend::paint( QPainter* painter )";
221#endif
222}
223
225{
226 int modelLabelsCount = 0;
227 for (DiagramObserver *observer : d->observers) {
228 AbstractDiagram *diagram = observer->diagram();
231 }
232 return modelLabelsCount;
233}
234
236{
237 if (area == d->referenceArea) {
238 return;
239 }
240 d->referenceArea = area;
241 setNeedRebuild();
242}
243
245{
246 return d->referenceArea ? d->referenceArea : qobject_cast<const QWidget *>(parent());
247}
248
250{
251 if (d->observers.isEmpty()) {
252 return nullptr;
253 }
254 return d->observers.first()->diagram();
255}
256
258{
260 for (int i = 0; i < d->observers.size(); ++i) {
261 list << d->observers.at(i)->diagram();
262 }
263 return list;
264}
265
267{
269 for (int i = 0; i < d->observers.size(); ++i) {
270 list << d->observers.at(i)->diagram();
271 }
272 return list;
273}
274
276{
277 if (newDiagram) {
278 auto *observer = new DiagramObserver(newDiagram, this);
279
280 DiagramObserver *oldObs = d->findObserverForDiagram(newDiagram);
281 if (oldObs) {
282 delete oldObs;
283 d->observers[d->observers.indexOf(oldObs)] = observer;
284 } else {
285 d->observers.append(observer);
286 }
287 connect(observer, &DiagramObserver::diagramAboutToBeDestroyed, this, &Legend::resetDiagram);
288 connect(observer, &DiagramObserver::diagramDataChanged, this, &Legend::setNeedRebuild);
289 connect(observer, &DiagramObserver::diagramDataHidden, this, &Legend::setNeedRebuild);
290 connect(observer, &DiagramObserver::diagramAttributesChanged, this, &Legend::setNeedRebuild);
291 setNeedRebuild();
292 }
293}
294
296{
297 int datasetBrushOffset = 0;
299 for (int i = 0; i < diagrams.count(); i++) {
300 if (diagrams.at(i) == oldDiagram) {
301 for (int i = 0; i < oldDiagram->datasetBrushes().count(); i++) {
302 d->brushes.remove(datasetBrushOffset + i);
303 d->texts.remove(datasetBrushOffset + i);
304 }
305 for (int i = 0; i < oldDiagram->datasetPens().count(); i++) {
306 d->pens.remove(datasetBrushOffset + i);
307 }
308 break;
309 }
310 datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
311 }
312
313 if (oldDiagram) {
314 DiagramObserver *oldObs = d->findObserverForDiagram(oldDiagram);
315 if (oldObs) {
316 d->observers.removeOne(oldObs);
317 delete oldObs;
318 }
319
320 // We might be in the middle of a KDChart dctor and hit the assertObjectType
321 // so queue the rebuild
322 QMetaObject::invokeMethod(this, &Legend::setNeedRebuild, Qt::QueuedConnection);
323 }
324}
325
327{
328 // removeDiagram() may change the d->observers list. So, build up the list of
329 // diagrams to remove first and then remove them one by one.
331 for (int i = 0; i < d->observers.size(); ++i) {
332 diagrams.append(d->observers.at(i)->diagram());
333 }
334 for (int i = 0; i < diagrams.count(); ++i) {
336 }
337}
338
340 AbstractDiagram *oldDiagram)
341{
343 if (!d->observers.isEmpty() && !old) {
344 old = d->observers.first()->diagram();
345 if (!old) {
346 d->observers.removeFirst(); // first entry had a 0 diagram
347 }
348 }
349 if (old) {
351 }
352 if (newDiagram) {
354 }
355}
356
358{
359 uint offset = 0;
360
361 for (int i = 0; i < d->observers.count(); ++i) {
362 if (d->observers.at(i)->diagram() == diagram) {
363 return offset;
364 }
365 AbstractDiagram *diagram = d->observers.at(i)->diagram();
366 if (!diagram->model()) {
367 continue;
368 }
369 offset = offset + diagram->model()->columnCount();
370 }
371
372 return offset;
373}
374
379
380void Legend::resetDiagram(AbstractDiagram *oldDiagram)
381{
383}
384
385void Legend::setVisible(bool visible)
386{
387 // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
388 // on the visibility of the parent.
390 emitPositionChanged();
391}
392
393void Legend::setNeedRebuild()
394{
395 buildLegend();
396 sizeHint();
397}
398
400{
401 if (d->position == position) {
402 return;
403 }
404 d->position = position;
405 emitPositionChanged();
406}
407
408void Legend::emitPositionChanged()
409{
412}
413
415{
416 return d->position;
417}
418
420{
421 if (d->alignment == alignment) {
422 return;
423 }
424 d->alignment = alignment;
425 emitPositionChanged();
426}
427
429{
430 return d->alignment;
431}
432
434{
435 if (d->textAlignment == alignment) {
436 return;
437 }
438 d->textAlignment = alignment;
439 emitPositionChanged();
440}
441
443{
444 return d->textAlignment;
445}
446
448{
449 if (d->legendLineSymbolAlignment == alignment) {
450 return;
451 }
452 d->legendLineSymbolAlignment = alignment;
453 emitPositionChanged();
454}
455
457{
458 return d->legendLineSymbolAlignment;
459}
460
462{
463 d->position = Position::Floating;
464 if (d->relativePosition != relativePosition) {
465 d->relativePosition = relativePosition;
466 emitPositionChanged();
467 }
468}
469
471{
472 return d->relativePosition;
473}
474
476{
477 if (d->orientation == orientation) {
478 return;
479 }
480 d->orientation = orientation;
481 setNeedRebuild();
482 emitPositionChanged();
483}
484
486{
487 return d->orientation;
488}
489
491{
492 if (d->order == order) {
493 return;
494 }
495 d->order = order;
496 setNeedRebuild();
497 emitPositionChanged();
498}
499
501{
502 return d->order;
503}
504
505void Legend::setShowLines(bool legendShowLines)
506{
507 if (d->showLines == legendShowLines) {
508 return;
509 }
510 d->showLines = legendShowLines;
511 setNeedRebuild();
512 emitPositionChanged();
513}
514
516{
517 return d->showLines;
518}
519
520void Legend::setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)
521{
522 d->useAutomaticMarkerSize = useAutomaticMarkerSize;
523 setNeedRebuild();
524 emitPositionChanged();
525}
526
528{
529 return d->useAutomaticMarkerSize;
530}
531
538{
539 if (!d->texts.count()) {
540 return;
541 }
542 d->texts.clear();
543 setNeedRebuild();
544}
545
546void Legend::setText(uint dataset, const QString &text)
547{
548 if (d->texts[dataset] == text) {
549 return;
550 }
551 d->texts[dataset] = text;
552 setNeedRebuild();
553}
554
555QString Legend::text(uint dataset) const
556{
557 if (d->texts.find(dataset) != d->texts.end()) {
558 return d->texts[dataset];
559 } else {
560 return d->modelLabels[dataset];
561 }
562}
563
565{
566 return d->texts;
567}
568
569void Legend::setColor(uint dataset, const QColor &color)
570{
571 if (d->brushes[dataset] != color) {
572 d->brushes[dataset] = color;
573 setNeedRebuild();
574 update();
575 }
576}
577
578void Legend::setBrush(uint dataset, const QBrush &brush)
579{
580 if (d->brushes[dataset] != brush) {
581 d->brushes[dataset] = brush;
582 setNeedRebuild();
583 update();
584 }
585}
586
587QBrush Legend::brush(uint dataset) const
588{
589 if (d->brushes.contains(dataset)) {
590 return d->brushes[dataset];
591 } else {
592 return d->modelBrushes[dataset];
593 }
594}
595
597{
598 return d->brushes;
599}
600
602{
603 bool changed = false;
604 QList<QBrush> datasetBrushes = diagram->datasetBrushes();
605 for (int i = 0; i < datasetBrushes.count(); i++) {
606 if (d->brushes[i] != datasetBrushes[i]) {
607 d->brushes[i] = datasetBrushes[i];
608 changed = true;
609 }
610 }
611 if (changed) {
612 setNeedRebuild();
613 update();
614 }
615}
616
617void Legend::setPen(uint dataset, const QPen &pen)
618{
619 if (d->pens[dataset] == pen) {
620 return;
621 }
622 d->pens[dataset] = pen;
623 setNeedRebuild();
624 update();
625}
626
627QPen Legend::pen(uint dataset) const
628{
629 if (d->pens.find(dataset) != d->pens.end()) {
630 return d->pens[dataset];
631 } else {
632 return d->modelPens[dataset];
633 }
634}
635
637{
638 return d->pens;
639}
640
641void Legend::setMarkerAttributes(uint dataset, const MarkerAttributes &markerAttributes)
642{
643 if (d->markerAttributes[dataset] == markerAttributes) {
644 return;
645 }
646 d->markerAttributes[dataset] = markerAttributes;
647 setNeedRebuild();
648 update();
649}
650
652{
653 if (d->markerAttributes.find(dataset) != d->markerAttributes.end()) {
654 return d->markerAttributes[dataset];
655 } else if (static_cast<uint>(d->modelMarkers.count()) > dataset) {
656 return d->modelMarkers[dataset];
657 } else {
658 return MarkerAttributes();
659 }
660}
661
663{
664 return d->markerAttributes;
665}
666
668{
669 if (d->textAttributes == a) {
670 return;
671 }
672 d->textAttributes = a;
673 setNeedRebuild();
674}
675
677{
678 return d->textAttributes;
679}
680
682{
683 if (d->titleText == text) {
684 return;
685 }
686 d->titleText = text;
687 setNeedRebuild();
688}
689
691{
692 return d->titleText;
693}
694
696{
697 if (d->titleTextAttributes == a) {
698 return;
699 }
700 d->titleTextAttributes = a;
701 setNeedRebuild();
702}
703
705{
706 return d->titleTextAttributes;
707}
708
710{
711#ifdef DEBUG_LEGEND_PAINT
712 qDebug() << "entering Legend::forceRebuild()";
713#endif
714 buildLegend();
715#ifdef DEBUG_LEGEND_PAINT
716 qDebug() << "leaving Legend::forceRebuild()";
717#endif
718}
719
720void Legend::setSpacing(uint space)
721{
722 if (d->spacing == space && d->layout->spacing() == int(space)) {
723 return;
724 }
725 d->spacing = space;
726 d->layout->setSpacing(space);
727 setNeedRebuild();
728}
729
730uint Legend::spacing() const
731{
732 return d->spacing;
733}
734
736{
738 for (int i = 0; i < pal.size(); i++) {
739 setBrush(i, pal.getBrush(i));
740 }
741}
742
744{
746 for (int i = 0; i < pal.size(); i++) {
747 setBrush(i, pal.getBrush(i));
748 }
749}
750
751void Legend::setSubduedColors(bool ordered)
752{
754 if (ordered) {
755 for (int i = 0; i < pal.size(); i++) {
756 setBrush(i, pal.getBrush(i));
757 }
758 } else {
759 static const int s_subduedColorsCount = 18;
761 static const int order[s_subduedColorsCount] = {
762 0, 5, 10, 15, 2, 7, 12, 17, 4,
763 9, 14, 1, 6, 11, 16, 3, 8, 13};
764 for (int i = 0; i < s_subduedColorsCount; i++) {
765 setBrush(i, pal.getBrush(order[i]));
766 }
767 }
768}
769
771{
773#ifdef DEBUG_LEGEND_PAINT
774 qDebug() << "Legend::resizeEvent() called";
775#endif
776 forceRebuild();
777 sizeHint();
778 QTimer::singleShot(0, this, &Legend::emitPositionChanged);
779}
780
781void Legend::Private::fetchPaintOptions(Legend *q)
782{
783 modelLabels.clear();
784 modelBrushes.clear();
785 modelPens.clear();
786 modelMarkers.clear();
787 // retrieve the diagrams' settings for all non-hidden datasets
788 for (int i = 0; i < observers.size(); ++i) {
789 const AbstractDiagram *diagram = observers.at(i)->diagram();
790 if (!diagram) {
791 continue;
792 }
793 const QStringList diagramLabels = diagram->datasetLabels();
794 const QList<QBrush> diagramBrushes = diagram->datasetBrushes();
795 const QList<QPen> diagramPens = diagram->datasetPens();
796 const QList<MarkerAttributes> diagramMarkers = diagram->datasetMarkers();
797
798 const bool ascend = q->sortOrder() == Qt::AscendingOrder;
799 int dataset = ascend ? 0 : diagramLabels.count() - 1;
800 const int end = ascend ? diagramLabels.count() : -1;
801 for (; dataset != end; dataset += ascend ? 1 : -1) {
802 if (diagram->isHidden(dataset) || q->datasetIsHidden(dataset)) {
803 continue;
804 }
805 modelLabels += diagramLabels[dataset];
806 modelBrushes += diagramBrushes[dataset];
807 modelPens += diagramPens[dataset];
808 modelMarkers += diagramMarkers[dataset];
809 }
810 }
811
812 Q_ASSERT(modelLabels.count() == modelBrushes.count());
813}
814
815QSizeF Legend::Private::markerSize(Legend *q, int dataset, qreal fontHeight) const
816{
817 QSizeF suppliedSize = q->markerAttributes(dataset).markerSize();
818 if (q->useAutomaticMarkerSize() || !suppliedSize.isValid()) {
819 return QSizeF(fontHeight, fontHeight);
820 } else {
821 return suppliedSize;
822 }
823}
824
825QSizeF Legend::Private::maxMarkerSize(Legend *q, qreal fontHeight) const
826{
827 QSizeF ret(1.0, 1.0);
828 if (q->legendStyle() != LinesOnly) {
829 for (int dataset = 0; dataset < modelLabels.count(); ++dataset) {
830 ret = ret.expandedTo(markerSize(q, dataset, fontHeight));
831 }
832 }
833 return ret;
834}
835
836HDatasetItem::HDatasetItem()
837{
838}
839
841{
842 while (w) {
843 if (w->isWindow()) {
844 // The null check has proved necessary during destruction of the Legend / Chart
845 if (w->layout()) {
846 w->layout()->update();
847 }
848 break;
849 } else {
850 w = qobject_cast<QWidget *>(w->parent());
851 Q_ASSERT(w);
852 }
853 }
854}
855
856void Legend::buildLegend()
857{
858 /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
859 line between title and dataset items, row two for each item: line, marker, text label and separator
860 line in that order.
861 In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
862 and (second row) line, marker, text label each. */
863 d->destroyOldLayout();
864
865 if (orientation() == Qt::Vertical) {
866 d->layout->setColumnStretch(6, 1);
867 } else {
868 d->layout->setColumnStretch(6, 0);
869 }
870
871 d->fetchPaintOptions(this);
872
876
877 // legend caption
878 if (!titleText().isEmpty() && titleTextAttributes().isVisible()) {
879 auto *titleItem =
881 measureOrientation, d->textAlignment);
882 titleItem->setParentWidget(this);
883
884 d->paintItems << titleItem;
885 d->layout->addItem(titleItem, 0, 0, 1, 5, Qt::AlignCenter);
886
887 // The line between the title and the legend items, if any.
888 if (showLines() && d->modelLabels.count()) {
890 d->paintItems << lineItem;
891 d->layout->addItem(lineItem, 1, 0, 1, 5, Qt::AlignCenter);
892 }
893 }
894
896 {
901 } else {
903 }
904 }
905
906 const QSizeF maxMarkerSize = d->maxMarkerSize(this, fontHeight);
907
908 // If we show a marker on a line, we paint it after 8 pixels
909 // of the line have been painted. This allows to see the line style
910 // at the right side of the marker without the line needing to
911 // be too long.
912 // (having the marker in the middle of the line would require longer lines)
913 const int lineLengthLeftOfMarker = 8;
914
915 int maxLineLength = 18;
916 {
917 bool hasComplexPenStyle = false;
918 for (int dataset = 0; dataset < d->modelLabels.count(); ++dataset) {
919 const QPen pn = pen(dataset);
920 const Qt::PenStyle ps = pn.style();
921 if (ps != Qt::NoPen) {
922 maxLineLength = qMax(pn.width() * 18, maxLineLength);
923 if (ps != Qt::SolidLine) {
924 hasComplexPenStyle = true;
925 }
926 }
927 }
930 }
931 }
932
933 // for all datasets: add (line)marker items and text items to the layout;
934 // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical
935 for (int dataset = 0; dataset < d->modelLabels.count(); ++dataset) {
936 const int vLayoutRow = 2 + dataset * 2;
938
939 // It is possible to set the marker brush through markerAttributes as well as
940 // the dataset brush set in the diagram - the markerAttributes have higher precedence.
942 markerAttrs.setMarkerSize(d->markerSize(this, dataset, fontHeight));
943 const QBrush markerBrush = markerAttrs.markerColor().isValid() ? QBrush(markerAttrs.markerColor()) : brush(dataset);
944
945 switch (legendStyle()) {
946 case MarkersOnly:
947 dsItem.markerLine = new MarkerLayoutItem(diagram(), markerAttrs, markerBrush,
949 break;
950 case LinesOnly:
952 d->legendLineSymbolAlignment, Qt::AlignCenter);
953 break;
954 case MarkersAndLines:
955 dsItem.markerLine = new LineWithMarkerLayoutItem(
957 markerBrush, markerAttrs.pen(), Qt::AlignCenter);
958 break;
959 default:
960 Q_ASSERT(false);
961 }
962
964 measureOrientation, d->textAlignment);
965 dsItem.label->setParentWidget(this);
966
967 // horizontal layout is deferred to flowDatasetItems()
968
969 if (orientation() == Qt::Horizontal) {
970 d->hLayoutDatasets << dsItem;
971 continue;
972 }
973
974 // (actual) vertical layout here
975
976 if (dsItem.markerLine) {
977 d->layout->addItem(dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter);
978 d->paintItems << dsItem.markerLine;
979 }
980 d->layout->addItem(dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter);
981 d->paintItems << dsItem.label;
982
983 // horizontal separator line, only between items
984 if (showLines() && dataset != d->modelLabels.count() - 1) {
987 d->paintItems << lineItem;
988 }
989 }
990
991 if (orientation() == Qt::Horizontal) {
992 d->flowHDatasetItems(this);
993 }
994
995 // vertical line (only in vertical mode)
996 if (orientation() == Qt::Vertical && showLines() && d->modelLabels.count()) {
998 d->paintItems << lineItem;
999 d->layout->addItem(lineItem, 2, 2, d->modelLabels.count() * 2, 1);
1000 }
1001
1003
1005#ifdef DEBUG_LEGEND_PAINT
1006 qDebug() << "leaving Legend::buildLegend()";
1007#endif
1008}
1009
1010int HDatasetItem::height() const
1011{
1012 return qMax(markerLine->sizeHint().height(), label->sizeHint().height());
1013}
1014
1015void Legend::Private::reflowHDatasetItems(Legend *q)
1016{
1017 if (hLayoutDatasets.isEmpty()) {
1018 return;
1019 }
1020
1021 paintItems.clear();
1022 // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the
1023 // caption and line under the caption! Those are easily identified because they aren't QLayouts.
1024 for (int i = layout->count() - 1; i >= 0; i--) {
1025 QLayoutItem *const item = layout->itemAt(i);
1026 QLayout *const hbox = item->layout();
1027 if (!hbox) {
1028 auto *alItem = dynamic_cast<AbstractLayoutItem *>(item);
1029 Q_ASSERT(alItem);
1030 paintItems << alItem;
1031 continue;
1032 }
1033 Q_ASSERT(dynamic_cast<QHBoxLayout *>(hbox));
1034 layout->takeAt(i);
1035 // detach children so they aren't deleted with the parent
1036 for (int j = hbox->count() - 1; j >= 0; j--) {
1037 hbox->takeAt(j);
1038 }
1039 delete hbox;
1040 }
1041
1042 flowHDatasetItems(q);
1043}
1044
1045// this works pretty much like flow layout for text, and it is only applicable to dataset items
1046// laid out horizontally
1047void Legend::Private::flowHDatasetItems(Legend *q)
1048{
1049 const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint()
1050
1051 const int allowedWidth = q->areaGeometry().width();
1052
1053 auto *currentLine = new QHBoxLayout;
1054 int mainLayoutRow = 1;
1055 layout->addItem(currentLine, mainLayoutRow++, /*column*/ 0,
1056 /*rowSpan*/ 1, /*columnSpan*/ 5, Qt::AlignLeft | Qt::AlignVCenter);
1057
1058 for (int dataset = 0; dataset < hLayoutDatasets.size(); dataset++) {
1059 HDatasetItem *hdsItem = &hLayoutDatasets[dataset];
1060
1061 bool spacerUsed = false;
1062 bool separatorUsed = false;
1063 if (!currentLine->isEmpty()) {
1064 const int separatorWidth = (q->showLines() ? separatorLineWidth : 0) + q->spacing();
1065 const int payloadWidth = hdsItem->markerLine->sizeHint().width() + hdsItem->label->sizeHint().width();
1066 if (currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth) {
1067 // too wide, "line break"
1068#ifdef DEBUG_LEGEND_PAINT
1069 qDebug() << Q_FUNC_INFO << "break" << mainLayoutRow
1070 << currentLine->sizeHint().width()
1071 << currentLine->sizeHint().width() + separatorWidth + payloadWidth
1072 << allowedWidth;
1073#endif
1074 currentLine = new QHBoxLayout;
1075 layout->addItem(currentLine, mainLayoutRow++, /*column*/ 0,
1076 /*rowSpan*/ 1, /*columnSpan*/ 5, Qt::AlignLeft | Qt::AlignVCenter);
1077 } else {
1078 // > 1 dataset item in line, put spacing and maybe a separator between them
1079 if (!hdsItem->spacer) {
1080 hdsItem->spacer = new QSpacerItem(q->spacing(), 1);
1081 }
1082 currentLine->addItem(hdsItem->spacer);
1083 spacerUsed = true;
1084
1085 if (q->showLines()) {
1086 if (!hdsItem->separatorLine) {
1087 hdsItem->separatorLine = new VerticalLineLayoutItem;
1088 }
1089 paintItems << hdsItem->separatorLine;
1090 currentLine->addItem(hdsItem->separatorLine);
1091 separatorUsed = true;
1092 }
1093 }
1094 }
1095 // those have no parents in the current layout, so they wouldn't get cleaned up otherwise
1096 if (!spacerUsed) {
1097 delete hdsItem->spacer;
1098 hdsItem->spacer = nullptr;
1099 }
1100 if (!separatorUsed) {
1101 delete hdsItem->separatorLine;
1102 hdsItem->separatorLine = nullptr;
1103 }
1104
1105 currentLine->addItem(hdsItem->markerLine);
1106 paintItems << hdsItem->markerLine;
1107 currentLine->addItem(hdsItem->label);
1108 paintItems << hdsItem->label;
1109 }
1110}
1111
1113{
1114 // this is better than using orientation() because, for layout purposes, we're not height-for-width
1115 // *yet* before buildLegend() has been called, and the layout logic might get upset if we say
1116 // something that will only be true in the future
1117 return !d->hLayoutDatasets.isEmpty();
1118}
1119
1120int Legend::heightForWidth(int width) const
1121{
1122 if (d->hLayoutDatasets.isEmpty()) {
1123 return -1;
1124 }
1125
1126 int ret = 0;
1127 // space for caption and line under caption (if any)
1128 for (int i = 0; i < 2; i++) {
1129 if (QLayoutItem *item = d->layout->itemAtPosition(i, 0)) {
1130 ret += item->sizeHint().height();
1131 }
1132 }
1133 const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint()
1134
1135 int currentLineWidth = 0;
1136 int currentLineHeight = 0;
1137 for (const HDatasetItem &hdsItem : qAsConst(d->hLayoutDatasets)) {
1138 const int payloadWidth = hdsItem.markerLine->sizeHint().width() + hdsItem.label->sizeHint().width();
1139 if (!currentLineWidth) {
1140 // first iteration
1142 } else {
1143 const int separatorWidth = (showLines() ? separatorLineWidth : 0) + spacing();
1145 if (currentLineWidth > width) {
1146 // too wide, "line break"
1147#ifdef DEBUG_LEGEND_PAINT
1148 qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth
1150 << width;
1151#endif
1155 }
1156 }
1158 }
1159 ret += currentLineHeight; // one less spacings than lines
1160 return ret;
1161}
1162
1163void Legend::Private::destroyOldLayout()
1164{
1165 // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items
1166 // (it isn't documented that QLayoutItems delete their children)
1167 for (int i = layout->count() - 1; i >= 0; i--) {
1168 delete layout->takeAt(i);
1169 }
1170 Q_ASSERT(!layout->count());
1171 hLayoutDatasets.clear();
1172 paintItems.clear();
1173}
1174
1175void Legend::setHiddenDatasets(const QList<uint> &hiddenDatasets)
1176{
1177 d->hiddenDatasets = hiddenDatasets;
1178}
1179
1181{
1182 return d->hiddenDatasets;
1183}
1184
1185void Legend::setDatasetHidden(uint dataset, bool hidden)
1186{
1187 if (hidden && !d->hiddenDatasets.contains(dataset)) {
1188 d->hiddenDatasets.append(dataset);
1189 } else if (!hidden && d->hiddenDatasets.contains(dataset)) {
1190 d->hiddenDatasets.removeAll(dataset);
1191 }
1192}
1193
1194bool Legend::datasetIsHidden(uint dataset) const
1195{
1196 return d->hiddenDatasets.contains(dataset);
1197}
static void updateToplevelLayout(QWidget *w)
@ MeasureCalculationModeAbsolute
@ MeasureOrientationMinimum
@ MeasureOrientationHorizontal
void setFrameAttributes(const FrameAttributes &a)
bool compare(const AbstractAreaBase *other) const
FrameAttributes frameAttributes() const
An area in the chart with a background, a frame, etc.
void positionChanged(AbstractAreaWidget *)
AbstractDiagram defines the interface for diagram classes.
QList< QBrush > datasetBrushes() const
QList< MarkerAttributes > datasetMarkers() const
A DiagramObserver watches the associated diagram for changes and deletion and emits corresponding sig...
void diagramDataHidden(AbstractDiagram *diagram)
void diagramAboutToBeDestroyed(AbstractDiagram *diagram)
void diagramDataChanged(AbstractDiagram *diagram)
void diagramAttributesChanged(AbstractDiagram *diagram)
A set of attributes for frames around items.
static QPaintDevice * paintDevice()
Legend defines the interface for the legend drawing class.
const RelativePosition floatingPosition() const
uint datasetCount() const
void setOrientation(Qt::Orientation orientation)
void setVisible(bool visible) override
Qt::Alignment textAlignment() const
Returns the alignment used while rendering text elements within the legend.
LegendStyle legendStyle() const
void propertiesChanged()
bool compare(const Legend *other) const
void destroyedLegend(Legend *)
void setSortOrder(Qt::SortOrder order)
const QMap< uint, QString > texts() const
const QMap< uint, QBrush > brushes() const
void setShowLines(bool legendShowLines)
void resetTexts()
Removes all legend texts that might have been set by setText.
QSize minimumSizeHint() const override
void paint(QPainter *painter) override
DiagramList diagrams() const
void setMarkerAttributes(uint dataset, const MarkerAttributes &)
ConstDiagramList constDiagrams() const
void setDiagram(KDChart::AbstractDiagram *newDiagram)
A convenience method doing the same as replaceDiagram( newDiagram, 0 );.
void forceRebuild() override
QBrush brush(uint dataset) const
void setPosition(Position position)
Specify the position of a non-floating legend.
uint spacing() const
QString titleText() const
void setHiddenDatasets(const QList< uint > &hiddenDatasets)
void setBrushesFromDiagram(KDChart::AbstractDiagram *diagram)
TextAttributes titleTextAttributes() const
Qt::Alignment legendSymbolAlignment() const
Returns the alignment used while drawing legend symbol(alignment of Legend::LinesOnly) within the leg...
MarkerAttributes markerAttributes(uint dataset) const
void replaceDiagram(KDChart::AbstractDiagram *newDiagram, KDChart::AbstractDiagram *oldDiagram=nullptr)
uint dataSetOffset(KDChart::AbstractDiagram *diagram)
void setDatasetHidden(uint dataset, bool hidden)
void resizeEvent(QResizeEvent *event) override
void needSizeHint() override
void setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)
const QMap< uint, QPen > pens() const
void setColor(uint dataset, const QColor &color)
void setLegendSymbolAlignment(Qt::Alignment)
Specify the alignment of the legend symbol( alignment of Legend::LinesOnly) within the legend.
virtual Legend * clone() const
void resizeLayout(const QSize &size) override
Qt::Orientation orientation() const
int heightForWidth(int width) const override
void addDiagram(KDChart::AbstractDiagram *newDiagram)
bool datasetIsHidden(uint dataset) const
Qt::Alignment alignment() const
void setPen(uint dataset, const QPen &pen)
QSize sizeHint() const override
void setReferenceArea(const QWidget *area)
const QWidget * referenceArea() const
void setTextAttributes(const TextAttributes &a)
void setTextAlignment(Qt::Alignment)
Specify the alignment of the text elements within the legend.
KDChart::AbstractDiagram * diagram() const
void setLegendStyle(LegendStyle style)
void removeDiagram(KDChart::AbstractDiagram *oldDiagram)
void setBrush(uint dataset, const QBrush &brush)
bool showLines() const
Position position() const
bool useAutomaticMarkerSize() const
void setSpacing(uint space)
Legend(QWidget *parent=nullptr)
void setText(uint dataset, const QString &text)
const QMap< uint, MarkerAttributes > markerAttributes() const
const QList< uint > hiddenDatasets() const
void setAlignment(Qt::Alignment)
Specify the alignment of a non-floating legend.
QPen pen(uint dataset) const
bool hasHeightForWidth() const override
void setFloatingPosition(const RelativePosition &relativePosition)
TextAttributes textAttributes() const
void setTitleTextAttributes(const TextAttributes &a)
Qt::SortOrder sortOrder() const
void setSubduedColors(bool ordered=false)
void setTitleText(const QString &text)
QString text(uint dataset) const
A set of attributes controlling the appearance of data set markers.
Measure is used to specify relative and absolute sizes in KDChart, e.g. font sizes.
A Palette is a set of brushes (or colors) to be used for painting data sets.
static const Palette & subduedPalette()
static const Palette & defaultPalette()
static const Palette & rainbowPalette()
Stores the absolute target points of a Position.
Defines a position, using compass terminology.
static const Position & Floating
static const Position & NorthEast
static const Position & NorthWest
Defines relative position information: reference area, position in this area (reference position),...
A set of text attributes.
qreal calculatedFontSize(const QSizeF &referenceSize, KDChartEnums::MeasureOrientation autoReferenceOrientation) const
Returns the font size that is used at drawing time.
virtual int columnCount(const QModelIndex &parent) const const=0
QAbstractItemModel * model() const const
virtual void addItem(QLayoutItem *item) override
void setPointSizeF(qreal pointSize)
qreal height() const const
virtual void addItem(QLayoutItem *item)=0
virtual int count() const const=0
virtual QLayoutItem * takeAt(int index)=0
void update()
virtual QLayout * layout()
virtual QSize sizeHint() const const=0
void append(const T &value)
const T & at(int i) const const
int count(const T &value) const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
T qobject_cast(QObject *object)
int width() const const
int height() const const
bool isValid() const const
AlignCenter
QueuedConnection
Orientation
PenStyle
SortOrder
virtual bool event(QEvent *event) override
bool isWindow() const const
QLayout * layout() const const
void setSizePolicy(QSizePolicy)
QStyle * style() const const
void update()
bool isVisible() const const

© 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 Apr 26 2024 00:04:57 for KD Chart API Documentation by doxygen 1.9.8