KD Chart API Documentation  3.1
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"
15 #include <KDChartAbstractDiagram.h>
16 #include <KDChartDiagramObserver.h>
18 #include <KDChartPalette.h>
19 #include <KDChartTextAttributes.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 
35 using namespace KDChart;
36 
37 Legend::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 
53 Legend::Private::~Private()
54 {
55  // this block left empty intentionally
56 }
57 
58 #define d d_func()
59 
60 Legend::Legend(QWidget *parent)
61  : AbstractAreaWidget(new Private(), parent)
62 {
63  d->referenceArea = parent;
64  init();
65 }
66 
67 Legend::Legend(AbstractDiagram *diagram, QWidget *parent)
68  : AbstractAreaWidget(new Private(), parent)
69 {
70  d->referenceArea = parent;
71  init();
73 }
74 
76 {
77  Q_EMIT destroyedLegend(this);
78 }
79 
80 void Legend::init()
81 {
82  setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
83 
84  d->layout = new QGridLayout(this);
85  d->layout->setContentsMargins(2, 2, 2, 2);
86  d->layout->setSpacing(d->spacing);
87 
88  const Measure normalFontSizeTitle(12, KDChartEnums::MeasureCalculationModeAbsolute);
89  const Measure normalFontSizeLabels(10, KDChartEnums::MeasureCalculationModeAbsolute);
90  const Measure minimalFontSize(4, KDChartEnums::MeasureCalculationModeAbsolute);
91 
92  TextAttributes textAttrs;
93  textAttrs.setPen(QPen(Qt::black));
94  textAttrs.setFont(QFont(QLatin1String("helvetica"), 10, QFont::Normal, false));
95  textAttrs.setFontSize(normalFontSizeLabels);
96  textAttrs.setMinimalFontSize(minimalFontSize);
97  setTextAttributes(textAttrs);
98 
99  TextAttributes titleTextAttrs;
100  titleTextAttrs.setPen(QPen(Qt::black));
101  titleTextAttrs.setFont(QFont(QLatin1String("helvetica"), 12, QFont::Bold, false));
102  titleTextAttrs.setFontSize(normalFontSizeTitle);
103  titleTextAttrs.setMinimalFontSize(minimalFontSize);
104  setTitleTextAttributes(titleTextAttrs);
105 
106  FrameAttributes frameAttrs;
107  frameAttrs.setVisible(true);
108  frameAttrs.setPen(QPen(Qt::black));
109  frameAttrs.setPadding(1);
110  setFrameAttributes(frameAttrs);
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 
123 QSize Legend::sizeHint() const
124 {
125 #ifdef DEBUG_LEGEND_PAINT
126  qDebug() << "Legend::sizeHint() started";
127 #endif
128  for (AbstractLayoutItem *paintItem : qAsConst(d->paintItems)) {
129  paintItem->sizeHint();
130  }
131  return AbstractAreaWidget::sizeHint();
132 }
133 
135 {
136  buildLegend();
137 }
138 
139 void Legend::resizeLayout(const QSize &size)
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 
154 void 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 
192 bool 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 
204 void Legend::paint(QPainter *painter)
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();
229  Q_ASSERT(diagram->datasetLabels().count() == diagram->datasetBrushes().count());
230  modelLabelsCount += diagram->datasetLabels().count();
231  }
232  return modelLabelsCount;
233 }
234 
235 void Legend::setReferenceArea(const QWidget *area)
236 {
237  if (area == d->referenceArea) {
238  return;
239  }
240  d->referenceArea = area;
241  setNeedRebuild();
242 }
243 
244 const QWidget *Legend::referenceArea() const
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 {
259  DiagramList list;
260  for (int i = 0; i < d->observers.size(); ++i) {
261  list << d->observers.at(i)->diagram();
262  }
263  return list;
264 }
265 
267 {
268  ConstDiagramList list;
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;
298  QList<AbstractDiagram *> diagrams = this->diagrams();
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.
330  QList<AbstractDiagram *> diagrams;
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 {
342  AbstractDiagram *old = oldDiagram;
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) {
350  removeDiagram(old);
351  }
352  if (newDiagram) {
353  addDiagram(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 
376 {
377  replaceDiagram(newDiagram);
378 }
379 
380 void Legend::resetDiagram(AbstractDiagram *oldDiagram)
381 {
382  removeDiagram(oldDiagram);
383 }
384 
385 void 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.
389  QWidget::setVisible(visible);
390  emitPositionChanged();
391 }
392 
393 void 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 
408 void Legend::emitPositionChanged()
409 {
410  Q_EMIT positionChanged(this);
411  Q_EMIT propertiesChanged();
412 }
413 
415 {
416  return d->position;
417 }
418 
419 void Legend::setAlignment(Qt::Alignment alignment)
420 {
421  if (d->alignment == alignment) {
422  return;
423  }
424  d->alignment = alignment;
425  emitPositionChanged();
426 }
427 
428 Qt::Alignment Legend::alignment() const
429 {
430  return d->alignment;
431 }
432 
433 void Legend::setTextAlignment(Qt::Alignment alignment)
434 {
435  if (d->textAlignment == alignment) {
436  return;
437  }
438  d->textAlignment = alignment;
439  emitPositionChanged();
440 }
441 
442 Qt::Alignment Legend::textAlignment() const
443 {
444  return d->textAlignment;
445 }
446 
447 void Legend::setLegendSymbolAlignment(Qt::Alignment alignment)
448 {
449  if (d->legendLineSymbolAlignment == alignment) {
450  return;
451  }
452  d->legendLineSymbolAlignment = alignment;
453  emitPositionChanged();
454 }
455 
456 Qt::Alignment Legend::legendSymbolAlignment() const
457 {
458  return d->legendLineSymbolAlignment;
459 }
460 
461 void Legend::setFloatingPosition(const RelativePosition &relativePosition)
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 
475 void Legend::setOrientation(Qt::Orientation orientation)
476 {
477  if (d->orientation == orientation) {
478  return;
479  }
480  d->orientation = orientation;
481  setNeedRebuild();
482  emitPositionChanged();
483 }
484 
485 Qt::Orientation Legend::orientation() const
486 {
487  return d->orientation;
488 }
489 
490 void Legend::setSortOrder(Qt::SortOrder order)
491 {
492  if (d->order == order) {
493  return;
494  }
495  d->order = order;
496  setNeedRebuild();
497  emitPositionChanged();
498 }
499 
500 Qt::SortOrder Legend::sortOrder() const
501 {
502  return d->order;
503 }
504 
505 void Legend::setShowLines(bool legendShowLines)
506 {
507  if (d->showLines == legendShowLines) {
508  return;
509  }
510  d->showLines = legendShowLines;
511  setNeedRebuild();
512  emitPositionChanged();
513 }
514 
515 bool Legend::showLines() const
516 {
517  return d->showLines;
518 }
519 
520 void 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 
546 void 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 
555 QString 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 
569 void 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 
578 void 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 
587 QBrush 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 
617 void 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 
627 QPen 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 
641 void 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 
681 void Legend::setTitleText(const QString &text)
682 {
683  if (d->titleText == text) {
684  return;
685  }
686  d->titleText = text;
687  setNeedRebuild();
688 }
689 
690 QString Legend::titleText() const
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 
720 void 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 
730 uint 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 
751 void 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;
760  Q_ASSERT(pal.size() >= s_subduedColorsCount);
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 
770 void Legend::resizeEvent(QResizeEvent *event)
771 {
772  Q_UNUSED(event);
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 
781 void 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 
815 QSizeF 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 
825 QSizeF 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 
836 HDatasetItem::HDatasetItem()
837 {
838 }
839 
840 static void updateToplevelLayout(QWidget *w)
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 
856 void 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 
873  const KDChartEnums::MeasureOrientation measureOrientation =
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()) {
889  auto *lineItem = new HorizontalLineLayoutItem;
890  d->paintItems << lineItem;
891  d->layout->addItem(lineItem, 1, 0, 1, 5, Qt::AlignCenter);
892  }
893  }
894 
895  qreal fontHeight = textAttributes().calculatedFontSize(referenceArea(), measureOrientation);
896  {
897  QFont tmpFont = textAttributes().font();
898  tmpFont.setPointSizeF(fontHeight);
900  fontHeight = QFontMetricsF(tmpFont, GlobalMeasureScaling::paintDevice()).height();
901  } else {
902  fontHeight = QFontMetricsF(tmpFont).height();
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  }
928  if (hasComplexPenStyle && legendStyle() != LinesOnly) {
929  maxLineLength += lineLengthLeftOfMarker + int(maxMarkerSize.width());
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;
937  HDatasetItem dsItem;
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.
941  MarkerAttributes markerAttrs = markerAttributes(dataset);
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,
948  markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter);
949  break;
950  case LinesOnly:
951  dsItem.markerLine = new LineLayoutItem(diagram(), maxLineLength, pen(dataset),
952  d->legendLineSymbolAlignment, Qt::AlignCenter);
953  break;
954  case MarkersAndLines:
955  dsItem.markerLine = new LineWithMarkerLayoutItem(
956  diagram(), maxLineLength, pen(dataset), lineLengthLeftOfMarker, markerAttrs,
957  markerBrush, markerAttrs.pen(), Qt::AlignCenter);
958  break;
959  default:
960  Q_ASSERT(false);
961  }
962 
963  dsItem.label = new TextLayoutItem(text(dataset), textAttributes(), referenceArea(),
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) {
985  auto *lineItem = new HorizontalLineLayoutItem;
986  d->layout->addItem(lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter);
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()) {
997  auto *lineItem = new VerticalLineLayoutItem;
998  d->paintItems << lineItem;
999  d->layout->addItem(lineItem, 2, 2, d->modelLabels.count() * 2, 1);
1000  }
1001 
1002  updateToplevelLayout(this);
1003 
1004  Q_EMIT propertiesChanged();
1005 #ifdef DEBUG_LEGEND_PAINT
1006  qDebug() << "leaving Legend::buildLegend()";
1007 #endif
1008 }
1009 
1010 int HDatasetItem::height() const
1011 {
1012  return qMax(markerLine->sizeHint().height(), label->sizeHint().height());
1013 }
1014 
1015 void 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
1047 void 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 
1120 int 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
1141  currentLineWidth = payloadWidth;
1142  } else {
1143  const int separatorWidth = (showLines() ? separatorLineWidth : 0) + spacing();
1144  currentLineWidth += separatorWidth + payloadWidth;
1145  if (currentLineWidth > width) {
1146  // too wide, "line break"
1147 #ifdef DEBUG_LEGEND_PAINT
1148  qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth
1149  << currentLineWidth + separatorWidth + payloadWidth
1150  << width;
1151 #endif
1152  ret += currentLineHeight + spacing();
1153  currentLineWidth = payloadWidth;
1154  currentLineHeight = 0;
1155  }
1156  }
1157  currentLineHeight = qMax(currentLineHeight, hdsItem.height());
1158  }
1159  ret += currentLineHeight; // one less spacings than lines
1160  return ret;
1161 }
1162 
1163 void 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 
1175 void Legend::setHiddenDatasets(const QList<uint> &hiddenDatasets)
1176 {
1177  d->hiddenDatasets = hiddenDatasets;
1178 }
1179 
1180 const QList<uint> Legend::hiddenDatasets() const
1181 {
1182  return d->hiddenDatasets;
1183 }
1184 
1185 void 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 
1194 bool Legend::datasetIsHidden(uint dataset) const
1195 {
1196  return d->hiddenDatasets.contains(dataset);
1197 }
#define d
static void updateToplevelLayout(QWidget *w)
@ MeasureCalculationModeAbsolute
Definition: KDChartEnums.h:215
@ MeasureOrientationMinimum
Definition: KDChartEnums.h:290
@ MeasureOrientationHorizontal
Definition: KDChartEnums.h:288
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< QPen > datasetPens() const
QList< MarkerAttributes > datasetMarkers() const
A DiagramObserver watches the associated diagram for changes and deletion and emits corresponding sig...
const AbstractDiagram * diagram() const
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.
Definition: KDChartLegend.h:44
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
~Legend() override
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.
void setMarkerSize(const QSizeF &size)
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.
QBrush getBrush(int position) const
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
Defines relative position information: reference area, position in this area (reference position),...
A set of text attributes.
void setFontSize(const Measure &measure)
void setMinimalFontSize(const Measure &measure)
void setPen(const QPen &pen)
void setFont(const QFont &font)
qreal calculatedFontSize(const QSizeF &referenceSize, KDChartEnums::MeasureOrientation autoReferenceOrientation) const
Returns the font size that is used at drawing time.
QList< const AbstractDiagram * > ConstDiagramList
Definition: KDChartLegend.h:24
QList< AbstractDiagram * > DiagramList
Definition: KDChartLegend.h:22

© 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 by doxygen 1.9.1