KD Chart API Documentation 3.0
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 Q_FOREACH (AbstractLayoutItem *paintItem, 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 Q_FOREACH (AbstractLayoutItem *paintItem, 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 Q_FOREACH (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, SIGNAL(diagramAboutToBeDestroyed(AbstractDiagram *)),
288 SLOT(resetDiagram(AbstractDiagram *)));
289 connect(observer, SIGNAL(diagramDataChanged(AbstractDiagram *)),
290 SLOT(setNeedRebuild()));
291 connect(observer, SIGNAL(diagramDataHidden(AbstractDiagram *)),
292 SLOT(setNeedRebuild()));
293 connect(observer, SIGNAL(diagramAttributesChanged(AbstractDiagram *)),
294 SLOT(setNeedRebuild()));
295 setNeedRebuild();
296 }
297}
298
300{
301 int datasetBrushOffset = 0;
303 for (int i = 0; i < diagrams.count(); i++) {
304 if (diagrams.at(i) == oldDiagram) {
305 for (int i = 0; i < oldDiagram->datasetBrushes().count(); i++) {
306 d->brushes.remove(datasetBrushOffset + i);
307 d->texts.remove(datasetBrushOffset + i);
308 }
309 for (int i = 0; i < oldDiagram->datasetPens().count(); i++) {
310 d->pens.remove(datasetBrushOffset + i);
311 }
312 break;
313 }
314 datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
315 }
316
317 if (oldDiagram) {
318 DiagramObserver *oldObs = d->findObserverForDiagram(oldDiagram);
319 if (oldObs) {
320 delete oldObs;
321 d->observers.removeAt(d->observers.indexOf(oldObs));
322 }
323 setNeedRebuild();
324 }
325}
326
328{
329 // removeDiagram() may change the d->observers list. So, build up the list of
330 // diagrams to remove first and then remove them one by one.
332 for (int i = 0; i < d->observers.size(); ++i) {
333 diagrams.append(d->observers.at(i)->diagram());
334 }
335 for (int i = 0; i < diagrams.count(); ++i) {
337 }
338}
339
341 AbstractDiagram *oldDiagram)
342{
344 if (!d->observers.isEmpty() && !old) {
345 old = d->observers.first()->diagram();
346 if (!old) {
347 d->observers.removeFirst(); // first entry had a 0 diagram
348 }
349 }
350 if (old) {
352 }
353 if (newDiagram) {
355 }
356}
357
359{
360 uint offset = 0;
361
362 for (int i = 0; i < d->observers.count(); ++i) {
363 if (d->observers.at(i)->diagram() == diagram) {
364 return offset;
365 }
366 AbstractDiagram *diagram = d->observers.at(i)->diagram();
367 if (!diagram->model()) {
368 continue;
369 }
370 offset = offset + diagram->model()->columnCount();
371 }
372
373 return offset;
374}
375
380
381void Legend::resetDiagram(AbstractDiagram *oldDiagram)
382{
384}
385
386void Legend::setVisible(bool visible)
387{
388 // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
389 // on the visibility of the parent.
391 emitPositionChanged();
392}
393
394void Legend::setNeedRebuild()
395{
396 buildLegend();
397 sizeHint();
398}
399
401{
402 if (d->position == position) {
403 return;
404 }
405 d->position = position;
406 emitPositionChanged();
407}
408
409void Legend::emitPositionChanged()
410{
411 emit positionChanged(this);
413}
414
416{
417 return d->position;
418}
419
421{
422 if (d->alignment == alignment) {
423 return;
424 }
425 d->alignment = alignment;
426 emitPositionChanged();
427}
428
430{
431 return d->alignment;
432}
433
435{
436 if (d->textAlignment == alignment) {
437 return;
438 }
439 d->textAlignment = alignment;
440 emitPositionChanged();
441}
442
444{
445 return d->textAlignment;
446}
447
449{
450 if (d->legendLineSymbolAlignment == alignment) {
451 return;
452 }
453 d->legendLineSymbolAlignment = alignment;
454 emitPositionChanged();
455}
456
458{
459 return d->legendLineSymbolAlignment;
460}
461
463{
464 d->position = Position::Floating;
465 if (d->relativePosition != relativePosition) {
466 d->relativePosition = relativePosition;
467 emitPositionChanged();
468 }
469}
470
472{
473 return d->relativePosition;
474}
475
477{
478 if (d->orientation == orientation) {
479 return;
480 }
481 d->orientation = orientation;
482 setNeedRebuild();
483 emitPositionChanged();
484}
485
487{
488 return d->orientation;
489}
490
492{
493 if (d->order == order) {
494 return;
495 }
496 d->order = order;
497 setNeedRebuild();
498 emitPositionChanged();
499}
500
502{
503 return d->order;
504}
505
506void Legend::setShowLines(bool legendShowLines)
507{
508 if (d->showLines == legendShowLines) {
509 return;
510 }
511 d->showLines = legendShowLines;
512 setNeedRebuild();
513 emitPositionChanged();
514}
515
517{
518 return d->showLines;
519}
520
521void Legend::setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)
522{
523 d->useAutomaticMarkerSize = useAutomaticMarkerSize;
524 setNeedRebuild();
525 emitPositionChanged();
526}
527
529{
530 return d->useAutomaticMarkerSize;
531}
532
539{
540 if (!d->texts.count()) {
541 return;
542 }
543 d->texts.clear();
544 setNeedRebuild();
545}
546
547void Legend::setText(uint dataset, const QString &text)
548{
549 if (d->texts[dataset] == text) {
550 return;
551 }
552 d->texts[dataset] = text;
553 setNeedRebuild();
554}
555
556QString Legend::text(uint dataset) const
557{
558 if (d->texts.find(dataset) != d->texts.end()) {
559 return d->texts[dataset];
560 } else {
561 return d->modelLabels[dataset];
562 }
563}
564
566{
567 return d->texts;
568}
569
570void Legend::setColor(uint dataset, const QColor &color)
571{
572 if (d->brushes[dataset] != color) {
573 d->brushes[dataset] = color;
574 setNeedRebuild();
575 update();
576 }
577}
578
579void Legend::setBrush(uint dataset, const QBrush &brush)
580{
581 if (d->brushes[dataset] != brush) {
582 d->brushes[dataset] = brush;
583 setNeedRebuild();
584 update();
585 }
586}
587
588QBrush Legend::brush(uint dataset) const
589{
590 if (d->brushes.contains(dataset)) {
591 return d->brushes[dataset];
592 } else {
593 return d->modelBrushes[dataset];
594 }
595}
596
598{
599 return d->brushes;
600}
601
603{
604 bool changed = false;
605 QList<QBrush> datasetBrushes = diagram->datasetBrushes();
606 for (int i = 0; i < datasetBrushes.count(); i++) {
607 if (d->brushes[i] != datasetBrushes[i]) {
608 d->brushes[i] = datasetBrushes[i];
609 changed = true;
610 }
611 }
612 if (changed) {
613 setNeedRebuild();
614 update();
615 }
616}
617
618void Legend::setPen(uint dataset, const QPen &pen)
619{
620 if (d->pens[dataset] == pen) {
621 return;
622 }
623 d->pens[dataset] = pen;
624 setNeedRebuild();
625 update();
626}
627
628QPen Legend::pen(uint dataset) const
629{
630 if (d->pens.find(dataset) != d->pens.end()) {
631 return d->pens[dataset];
632 } else {
633 return d->modelPens[dataset];
634 }
635}
636
638{
639 return d->pens;
640}
641
642void Legend::setMarkerAttributes(uint dataset, const MarkerAttributes &markerAttributes)
643{
644 if (d->markerAttributes[dataset] == markerAttributes) {
645 return;
646 }
647 d->markerAttributes[dataset] = markerAttributes;
648 setNeedRebuild();
649 update();
650}
651
653{
654 if (d->markerAttributes.find(dataset) != d->markerAttributes.end()) {
655 return d->markerAttributes[dataset];
656 } else if (static_cast<uint>(d->modelMarkers.count()) > dataset) {
657 return d->modelMarkers[dataset];
658 } else {
659 return MarkerAttributes();
660 }
661}
662
664{
665 return d->markerAttributes;
666}
667
669{
670 if (d->textAttributes == a) {
671 return;
672 }
673 d->textAttributes = a;
674 setNeedRebuild();
675}
676
678{
679 return d->textAttributes;
680}
681
683{
684 if (d->titleText == text) {
685 return;
686 }
687 d->titleText = text;
688 setNeedRebuild();
689}
690
692{
693 return d->titleText;
694}
695
697{
698 if (d->titleTextAttributes == a) {
699 return;
700 }
701 d->titleTextAttributes = a;
702 setNeedRebuild();
703}
704
706{
707 return d->titleTextAttributes;
708}
709
711{
712#ifdef DEBUG_LEGEND_PAINT
713 qDebug() << "entering Legend::forceRebuild()";
714#endif
715 buildLegend();
716#ifdef DEBUG_LEGEND_PAINT
717 qDebug() << "leaving Legend::forceRebuild()";
718#endif
719}
720
721void Legend::setSpacing(uint space)
722{
723 if (d->spacing == space && d->layout->spacing() == int(space)) {
724 return;
725 }
726 d->spacing = space;
727 d->layout->setSpacing(space);
728 setNeedRebuild();
729}
730
731uint Legend::spacing() const
732{
733 return d->spacing;
734}
735
737{
739 for (int i = 0; i < pal.size(); i++) {
740 setBrush(i, pal.getBrush(i));
741 }
742}
743
745{
747 for (int i = 0; i < pal.size(); i++) {
748 setBrush(i, pal.getBrush(i));
749 }
750}
751
752void Legend::setSubduedColors(bool ordered)
753{
755 if (ordered) {
756 for (int i = 0; i < pal.size(); i++) {
757 setBrush(i, pal.getBrush(i));
758 }
759 } else {
760 static const int s_subduedColorsCount = 18;
762 static const int order[s_subduedColorsCount] = {
763 0, 5, 10, 15, 2, 7, 12, 17, 4,
764 9, 14, 1, 6, 11, 16, 3, 8, 13};
765 for (int i = 0; i < s_subduedColorsCount; i++) {
766 setBrush(i, pal.getBrush(order[i]));
767 }
768 }
769}
770
772{
774#ifdef DEBUG_LEGEND_PAINT
775 qDebug() << "Legend::resizeEvent() called";
776#endif
777 forceRebuild();
778 sizeHint();
779 QTimer::singleShot(0, this, SLOT(emitPositionChanged()));
780}
781
782void Legend::Private::fetchPaintOptions(Legend *q)
783{
784 modelLabels.clear();
785 modelBrushes.clear();
786 modelPens.clear();
787 modelMarkers.clear();
788 // retrieve the diagrams' settings for all non-hidden datasets
789 for (int i = 0; i < observers.size(); ++i) {
790 const AbstractDiagram *diagram = observers.at(i)->diagram();
791 if (!diagram) {
792 continue;
793 }
794 const QStringList diagramLabels = diagram->datasetLabels();
795 const QList<QBrush> diagramBrushes = diagram->datasetBrushes();
796 const QList<QPen> diagramPens = diagram->datasetPens();
797 const QList<MarkerAttributes> diagramMarkers = diagram->datasetMarkers();
798
799 const bool ascend = q->sortOrder() == Qt::AscendingOrder;
800 int dataset = ascend ? 0 : diagramLabels.count() - 1;
801 const int end = ascend ? diagramLabels.count() : -1;
802 for (; dataset != end; dataset += ascend ? 1 : -1) {
803 if (diagram->isHidden(dataset) || q->datasetIsHidden(dataset)) {
804 continue;
805 }
806 modelLabels += diagramLabels[dataset];
807 modelBrushes += diagramBrushes[dataset];
808 modelPens += diagramPens[dataset];
809 modelMarkers += diagramMarkers[dataset];
810 }
811 }
812
813 Q_ASSERT(modelLabels.count() == modelBrushes.count());
814}
815
816QSizeF Legend::Private::markerSize(Legend *q, int dataset, qreal fontHeight) const
817{
818 QSizeF suppliedSize = q->markerAttributes(dataset).markerSize();
819 if (q->useAutomaticMarkerSize() || !suppliedSize.isValid()) {
820 return QSizeF(fontHeight, fontHeight);
821 } else {
822 return suppliedSize;
823 }
824}
825
826QSizeF Legend::Private::maxMarkerSize(Legend *q, qreal fontHeight) const
827{
828 QSizeF ret(1.0, 1.0);
829 if (q->legendStyle() != LinesOnly) {
830 for (int dataset = 0; dataset < modelLabels.count(); ++dataset) {
831 ret = ret.expandedTo(markerSize(q, dataset, fontHeight));
832 }
833 }
834 return ret;
835}
836
837HDatasetItem::HDatasetItem()
838{
839}
840
842{
843 while (w) {
844 if (w->isWindow()) {
845 // The null check has proved necessary during destruction of the Legend / Chart
846 if (w->layout()) {
847 w->layout()->update();
848 }
849 break;
850 } else {
851 w = qobject_cast<QWidget *>(w->parent());
852 Q_ASSERT(w);
853 }
854 }
855}
856
857void Legend::buildLegend()
858{
859 /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
860 line between title and dataset items, row two for each item: line, marker, text label and separator
861 line in that order.
862 In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
863 and (second row) line, marker, text label each. */
864 d->destroyOldLayout();
865
866 if (orientation() == Qt::Vertical) {
867 d->layout->setColumnStretch(6, 1);
868 } else {
869 d->layout->setColumnStretch(6, 0);
870 }
871
872 d->fetchPaintOptions(this);
873
877
878 // legend caption
879 if (!titleText().isEmpty() && titleTextAttributes().isVisible()) {
880 auto *titleItem =
882 measureOrientation, d->textAlignment);
883 titleItem->setParentWidget(this);
884
885 d->paintItems << titleItem;
886 d->layout->addItem(titleItem, 0, 0, 1, 5, Qt::AlignCenter);
887
888 // The line between the title and the legend items, if any.
889 if (showLines() && d->modelLabels.count()) {
891 d->paintItems << lineItem;
892 d->layout->addItem(lineItem, 1, 0, 1, 5, Qt::AlignCenter);
893 }
894 }
895
897 {
902 } else {
904 }
905 }
906
907 const QSizeF maxMarkerSize = d->maxMarkerSize(this, fontHeight);
908
909 // If we show a marker on a line, we paint it after 8 pixels
910 // of the line have been painted. This allows to see the line style
911 // at the right side of the marker without the line needing to
912 // be too long.
913 // (having the marker in the middle of the line would require longer lines)
914 const int lineLengthLeftOfMarker = 8;
915
916 int maxLineLength = 18;
917 {
918 bool hasComplexPenStyle = false;
919 for (int dataset = 0; dataset < d->modelLabels.count(); ++dataset) {
920 const QPen pn = pen(dataset);
921 const Qt::PenStyle ps = pn.style();
922 if (ps != Qt::NoPen) {
923 maxLineLength = qMax(pn.width() * 18, maxLineLength);
924 if (ps != Qt::SolidLine) {
925 hasComplexPenStyle = true;
926 }
927 }
928 }
931 }
932 }
933
934 // for all datasets: add (line)marker items and text items to the layout;
935 // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical
936 for (int dataset = 0; dataset < d->modelLabels.count(); ++dataset) {
937 const int vLayoutRow = 2 + dataset * 2;
939
940 // It is possible to set the marker brush through markerAttributes as well as
941 // the dataset brush set in the diagram - the markerAttributes have higher precedence.
943 markerAttrs.setMarkerSize(d->markerSize(this, dataset, fontHeight));
944 const QBrush markerBrush = markerAttrs.markerColor().isValid() ? QBrush(markerAttrs.markerColor()) : brush(dataset);
945
946 switch (legendStyle()) {
947 case MarkersOnly:
948 dsItem.markerLine = new MarkerLayoutItem(diagram(), markerAttrs, markerBrush,
950 break;
951 case LinesOnly:
953 d->legendLineSymbolAlignment, Qt::AlignCenter);
954 break;
955 case MarkersAndLines:
956 dsItem.markerLine = new LineWithMarkerLayoutItem(
958 markerBrush, markerAttrs.pen(), Qt::AlignCenter);
959 break;
960 default:
961 Q_ASSERT(false);
962 }
963
965 measureOrientation, d->textAlignment);
966 dsItem.label->setParentWidget(this);
967
968 // horizontal layout is deferred to flowDatasetItems()
969
970 if (orientation() == Qt::Horizontal) {
971 d->hLayoutDatasets << dsItem;
972 continue;
973 }
974
975 // (actual) vertical layout here
976
977 if (dsItem.markerLine) {
978 d->layout->addItem(dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter);
979 d->paintItems << dsItem.markerLine;
980 }
981 d->layout->addItem(dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter);
982 d->paintItems << dsItem.label;
983
984 // horizontal separator line, only between items
985 if (showLines() && dataset != d->modelLabels.count() - 1) {
988 d->paintItems << lineItem;
989 }
990 }
991
992 if (orientation() == Qt::Horizontal) {
993 d->flowHDatasetItems(this);
994 }
995
996 // vertical line (only in vertical mode)
997 if (orientation() == Qt::Vertical && showLines() && d->modelLabels.count()) {
999 d->paintItems << lineItem;
1000 d->layout->addItem(lineItem, 2, 2, d->modelLabels.count() * 2, 1);
1001 }
1002
1004
1006#ifdef DEBUG_LEGEND_PAINT
1007 qDebug() << "leaving Legend::buildLegend()";
1008#endif
1009}
1010
1011int HDatasetItem::height() const
1012{
1013 return qMax(markerLine->sizeHint().height(), label->sizeHint().height());
1014}
1015
1016void Legend::Private::reflowHDatasetItems(Legend *q)
1017{
1018 if (hLayoutDatasets.isEmpty()) {
1019 return;
1020 }
1021
1022 paintItems.clear();
1023 // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the
1024 // caption and line under the caption! Those are easily identified because they aren't QLayouts.
1025 for (int i = layout->count() - 1; i >= 0; i--) {
1026 QLayoutItem *const item = layout->itemAt(i);
1027 QLayout *const hbox = item->layout();
1028 if (!hbox) {
1029 auto *alItem = dynamic_cast<AbstractLayoutItem *>(item);
1030 Q_ASSERT(alItem);
1031 paintItems << alItem;
1032 continue;
1033 }
1034 Q_ASSERT(dynamic_cast<QHBoxLayout *>(hbox));
1035 layout->takeAt(i);
1036 // detach children so they aren't deleted with the parent
1037 for (int j = hbox->count() - 1; j >= 0; j--) {
1038 hbox->takeAt(j);
1039 }
1040 delete hbox;
1041 }
1042
1043 flowHDatasetItems(q);
1044}
1045
1046// this works pretty much like flow layout for text, and it is only applicable to dataset items
1047// laid out horizontally
1048void Legend::Private::flowHDatasetItems(Legend *q)
1049{
1050 const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint()
1051
1052 const int allowedWidth = q->areaGeometry().width();
1053
1054 auto *currentLine = new QHBoxLayout;
1055 int mainLayoutRow = 1;
1056 layout->addItem(currentLine, mainLayoutRow++, /*column*/ 0,
1057 /*rowSpan*/ 1, /*columnSpan*/ 5, Qt::AlignLeft | Qt::AlignVCenter);
1058
1059 for (int dataset = 0; dataset < hLayoutDatasets.size(); dataset++) {
1060 HDatasetItem *hdsItem = &hLayoutDatasets[dataset];
1061
1062 bool spacerUsed = false;
1063 bool separatorUsed = false;
1064 if (!currentLine->isEmpty()) {
1065 const int separatorWidth = (q->showLines() ? separatorLineWidth : 0) + q->spacing();
1066 const int payloadWidth = hdsItem->markerLine->sizeHint().width() + hdsItem->label->sizeHint().width();
1067 if (currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth) {
1068 // too wide, "line break"
1069#ifdef DEBUG_LEGEND_PAINT
1070 qDebug() << Q_FUNC_INFO << "break" << mainLayoutRow
1071 << currentLine->sizeHint().width()
1072 << currentLine->sizeHint().width() + separatorWidth + payloadWidth
1073 << allowedWidth;
1074#endif
1075 currentLine = new QHBoxLayout;
1076 layout->addItem(currentLine, mainLayoutRow++, /*column*/ 0,
1077 /*rowSpan*/ 1, /*columnSpan*/ 5, Qt::AlignLeft | Qt::AlignVCenter);
1078 } else {
1079 // > 1 dataset item in line, put spacing and maybe a separator between them
1080 if (!hdsItem->spacer) {
1081 hdsItem->spacer = new QSpacerItem(q->spacing(), 1);
1082 }
1083 currentLine->addItem(hdsItem->spacer);
1084 spacerUsed = true;
1085
1086 if (q->showLines()) {
1087 if (!hdsItem->separatorLine) {
1088 hdsItem->separatorLine = new VerticalLineLayoutItem;
1089 }
1090 paintItems << hdsItem->separatorLine;
1091 currentLine->addItem(hdsItem->separatorLine);
1092 separatorUsed = true;
1093 }
1094 }
1095 }
1096 // those have no parents in the current layout, so they wouldn't get cleaned up otherwise
1097 if (!spacerUsed) {
1098 delete hdsItem->spacer;
1099 hdsItem->spacer = nullptr;
1100 }
1101 if (!separatorUsed) {
1102 delete hdsItem->separatorLine;
1103 hdsItem->separatorLine = nullptr;
1104 }
1105
1106 currentLine->addItem(hdsItem->markerLine);
1107 paintItems << hdsItem->markerLine;
1108 currentLine->addItem(hdsItem->label);
1109 paintItems << hdsItem->label;
1110 }
1111}
1112
1114{
1115 // this is better than using orientation() because, for layout purposes, we're not height-for-width
1116 // *yet* before buildLegend() has been called, and the layout logic might get upset if we say
1117 // something that will only be true in the future
1118 return !d->hLayoutDatasets.isEmpty();
1119}
1120
1121int Legend::heightForWidth(int width) const
1122{
1123 if (d->hLayoutDatasets.isEmpty()) {
1124 return -1;
1125 }
1126
1127 int ret = 0;
1128 // space for caption and line under caption (if any)
1129 for (int i = 0; i < 2; i++) {
1130 if (QLayoutItem *item = d->layout->itemAtPosition(i, 0)) {
1131 ret += item->sizeHint().height();
1132 }
1133 }
1134 const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint()
1135
1136 int currentLineWidth = 0;
1137 int currentLineHeight = 0;
1138 Q_FOREACH (const HDatasetItem &hdsItem, d->hLayoutDatasets) {
1139 const int payloadWidth = hdsItem.markerLine->sizeHint().width() + hdsItem.label->sizeHint().width();
1140 if (!currentLineWidth) {
1141 // first iteration
1143 } else {
1144 const int separatorWidth = (showLines() ? separatorLineWidth : 0) + spacing();
1146 if (currentLineWidth > width) {
1147 // too wide, "line break"
1148#ifdef DEBUG_LEGEND_PAINT
1149 qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth
1151 << width;
1152#endif
1156 }
1157 }
1159 }
1160 ret += currentLineHeight; // one less spacings than lines
1161 return ret;
1162}
1163
1164void Legend::Private::destroyOldLayout()
1165{
1166 // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items
1167 // (it isn't documented that QLayoutItems delete their children)
1168 for (int i = layout->count() - 1; i >= 0; i--) {
1169 delete layout->takeAt(i);
1170 }
1171 Q_ASSERT(!layout->count());
1172 hLayoutDatasets.clear();
1173 paintItems.clear();
1174}
1175
1176void Legend::setHiddenDatasets(const QList<uint> &hiddenDatasets)
1177{
1178 d->hiddenDatasets = hiddenDatasets;
1179}
1180
1182{
1183 return d->hiddenDatasets;
1184}
1185
1186void Legend::setDatasetHidden(uint dataset, bool hidden)
1187{
1188 if (hidden && !d->hiddenDatasets.contains(dataset)) {
1189 d->hiddenDatasets.append(dataset);
1190 } else if (!hidden && d->hiddenDatasets.contains(dataset)) {
1191 d->hiddenDatasets.removeAll(dataset);
1192 }
1193}
1194
1195bool Legend::datasetIsHidden(uint dataset) const
1196{
1197 return d->hiddenDatasets.contains(dataset);
1198}
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...
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
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
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 Feb 23 2024 00:02:58 for KD Chart API Documentation by doxygen 1.9.8