KD Chart 2  [rev.2.7]
KDChartLegend.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2020 Klaralvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Chart library.
5 **
6 ** Licensees holding valid commercial KD Chart licenses may use this file in
7 ** accordance with the KD Chart Commercial License Agreement provided with
8 ** the Software.
9 **
10 **
11 ** This file may be distributed and/or modified under the terms of the
12 ** GNU General Public License version 2 and version 3 as published by the
13 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
14 **
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 **
18 ** Contact info@kdab.com if any conditions of this licensing are not
19 ** clear to you.
20 **
21 **********************************************************************/
22 
23 #include "KDChartLegend.h"
24 #include "KDChartLegend_p.h"
25 #include <KDChartTextAttributes.h>
27 #include <KDChartPalette.h>
28 #include <KDChartAbstractDiagram.h>
29 #include "KDTextDocument.h"
30 #include <KDChartDiagramObserver.h>
31 #include "KDChartLayoutItems.h"
32 
33 #include <QFont>
34 #include <QGridLayout>
35 #include <QPainter>
36 #include <QTextTableCell>
37 #include <QTextCursor>
38 #include <QTextCharFormat>
39 #include <QTextDocumentFragment>
40 #include <QTimer>
41 #include <QAbstractTextDocumentLayout>
42 #include <QtDebug>
43 #include <QLabel>
44 
45 #include <KDABLibFakes>
46 
47 using namespace KDChart;
48 
49 Legend::Private::Private() :
50  referenceArea( 0 ),
51  position( Position::East ),
52  alignment( Qt::AlignCenter ),
53  textAlignment( Qt::AlignCenter ),
54  relativePosition( RelativePosition() ),
55  orientation( Qt::Vertical ),
56  order( Qt::AscendingOrder ),
57  showLines( false ),
58  titleText( QObject::tr( "Legend" ) ),
59  spacing( 1 ),
60  useAutomaticMarkerSize( true ),
61  legendStyle( MarkersOnly )
62 {
63  // By default we specify a simple, hard point as the 'relative' position's ref. point,
64  // since we can not be sure that there will be any parent specified for the legend.
65  relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) );
66  relativePosition.setReferencePosition( Position::NorthWest );
67  relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft );
68  relativePosition.setHorizontalPadding( Measure( 4.0, KDChartEnums::MeasureCalculationModeAbsolute ) );
69  relativePosition.setVerticalPadding( Measure( 4.0, KDChartEnums::MeasureCalculationModeAbsolute ) );
70 }
71 
72 Legend::Private::~Private()
73 {
74  // this bloc left empty intentionally
75 }
76 
77 
78 #define d d_func()
79 
80 
81 Legend::Legend( QWidget* parent ) :
82  AbstractAreaWidget( new Private(), parent )
83 {
84  d->referenceArea = parent;
85  init();
86 }
87 
89  AbstractAreaWidget( new Private(), parent )
90 {
91  d->referenceArea = parent;
92  init();
93  setDiagram( diagram );
94 }
95 
97 {
98  emit destroyedLegend( this );
99 }
100 
101 void Legend::init()
102 {
103  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
104 
105  d->layout = new QGridLayout( this );
106  d->layout->setMargin( 2 );
107  d->layout->setSpacing( d->spacing );
108 
109  const Measure normalFontSizeTitle( 12, KDChartEnums::MeasureCalculationModeAbsolute );
110  const Measure normalFontSizeLabels( 10, KDChartEnums::MeasureCalculationModeAbsolute );
111  const Measure minimalFontSize( 4, KDChartEnums::MeasureCalculationModeAbsolute );
112 
113  TextAttributes textAttrs;
114  textAttrs.setPen( QPen( Qt::black ) );
115  textAttrs.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Normal, false ) );
116  textAttrs.setFontSize( normalFontSizeLabels );
117  textAttrs.setMinimalFontSize( minimalFontSize );
118  setTextAttributes( textAttrs );
119 
120  TextAttributes titleTextAttrs;
121  titleTextAttrs.setPen( QPen( Qt::black ) );
122  titleTextAttrs.setFont( QFont( QLatin1String( "helvetica" ), 12, QFont::Bold, false ) );
123  titleTextAttrs.setFontSize( normalFontSizeTitle );
124  titleTextAttrs.setMinimalFontSize( minimalFontSize );
125  setTitleTextAttributes( titleTextAttrs );
126 
127  FrameAttributes frameAttrs;
128  frameAttrs.setVisible( true );
129  frameAttrs.setPen( QPen( Qt::black ) );
130  frameAttrs.setPadding( 1 );
131  setFrameAttributes( frameAttrs );
132 
133  d->position = Position::NorthEast;
134  d->alignment = Qt::AlignCenter;
135 }
136 
137 
139 {
140  return sizeHint();
141 }
142 
143 //#define DEBUG_LEGEND_PAINT
144 
145 QSize Legend::sizeHint() const
146 {
147 #ifdef DEBUG_LEGEND_PAINT
148  qDebug() << "Legend::sizeHint() started";
149 #endif
150  Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) {
151  paintItem->sizeHint();
152  }
153  return AbstractAreaWidget::sizeHint();
154 }
155 
157 {
158  buildLegend();
159 }
160 
161 void Legend::resizeLayout( const QSize& size )
162 {
163 #ifdef DEBUG_LEGEND_PAINT
164  qDebug() << "Legend::resizeLayout started";
165 #endif
166  if ( d->layout ) {
167  d->reflowHDatasetItems( this );
168  d->layout->setGeometry( QRect(QPoint( 0,0 ), size) );
169  activateTheLayout();
170  }
171 #ifdef DEBUG_LEGEND_PAINT
172  qDebug() << "Legend::resizeLayout done";
173 #endif
174 }
175 
176 void Legend::activateTheLayout()
177 {
178  if ( d->layout && d->layout->parent() ) {
179  d->layout->activate();
180  }
181 }
182 
184 {
185  if ( d->legendStyle == style ) {
186  return;
187  }
188  d->legendStyle = style;
189  setNeedRebuild();
190 }
191 
193 {
194  return d->legendStyle;
195 }
196 
201 {
202  Legend* legend = new Legend( new Private( *d ), 0 );
203  legend->setTextAttributes( textAttributes() );
205  legend->setFrameAttributes( frameAttributes() );
207  legend->setPosition( position() );
208  legend->setAlignment( alignment() );
209  legend->setTextAlignment( textAlignment() );
210  legend->setLegendStyle( legendStyle() );
211  return legend;
212 }
213 
214 
215 bool Legend::compare( const Legend* other ) const
216 {
217  if ( other == this ) {
218  return true;
219  }
220  if ( !other ) {
221  return false;
222  }
223 
224  return ( AbstractAreaBase::compare( other ) ) &&
225  (isVisible() == other->isVisible()) &&
226  (position() == other->position()) &&
227  (alignment() == other->alignment())&&
228  (textAlignment() == other->textAlignment())&&
229  (floatingPosition() == other->floatingPosition()) &&
230  (orientation() == other->orientation())&&
231  (showLines() == other->showLines())&&
232  (texts() == other->texts())&&
233  (brushes() == other->brushes())&&
234  (pens() == other->pens())&&
235  (markerAttributes() == other->markerAttributes())&&
237  (textAttributes() == other->textAttributes()) &&
238  (titleText() == other->titleText())&&
239  (titleTextAttributes() == other->titleTextAttributes()) &&
240  (spacing() == other->spacing()) &&
241  (legendStyle() == other->legendStyle());
242 }
243 
244 
245 void Legend::paint( QPainter* painter )
246 {
247 #ifdef DEBUG_LEGEND_PAINT
248  qDebug() << "entering Legend::paint( QPainter* painter )";
249 #endif
250  if ( !diagram() ) {
251  return;
252  }
253 
254  activateTheLayout();
255 
256  Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) {
257  paintItem->paint( painter );
258  }
259 
260 #ifdef DEBUG_LEGEND_PAINT
261  qDebug() << "leaving Legend::paint( QPainter* painter )";
262 #endif
263 }
264 
265 
267 {
268  int modelLabelsCount = 0;
269  KDAB_FOREACH ( DiagramObserver* observer, d->observers ) {
270  AbstractDiagram* diagram = observer->diagram();
271  Q_ASSERT( diagram->datasetLabels().count() == diagram->datasetBrushes().count() );
272  modelLabelsCount += diagram->datasetLabels().count();
273  }
274  return modelLabelsCount;
275 }
276 
277 
279 {
280  if ( area == d->referenceArea ) {
281  return;
282  }
283  d->referenceArea = area;
284  setNeedRebuild();
285 }
286 
288 {
289  return d->referenceArea ? d->referenceArea : qobject_cast< const QWidget* >( parent() );
290 }
291 
292 
294 {
295  if ( d->observers.isEmpty() ) {
296  return 0;
297  }
298  return d->observers.first()->diagram();
299 }
300 
302 {
303  DiagramList list;
304  for ( int i = 0; i < d->observers.size(); ++i ) {
305  list << d->observers.at(i)->diagram();
306  }
307  return list;
308 }
309 
311 {
312  ConstDiagramList list;
313  for ( int i = 0; i < d->observers.size(); ++i ) {
314  list << d->observers.at(i)->diagram();
315  }
316  return list;
317 }
318 
320 {
321  if ( newDiagram ) {
322  DiagramObserver* observer = new DiagramObserver( newDiagram, this );
323 
324  DiagramObserver* oldObs = d->findObserverForDiagram( newDiagram );
325  if ( oldObs ) {
326  delete oldObs;
327  d->observers[ d->observers.indexOf( oldObs ) ] = observer;
328  } else {
329  d->observers.append( observer );
330  }
331  connect( observer, SIGNAL( diagramAboutToBeDestroyed(AbstractDiagram*) ),
332  SLOT( resetDiagram(AbstractDiagram*) ));
333  connect( observer, SIGNAL( diagramDataChanged(AbstractDiagram*) ),
334  SLOT( setNeedRebuild() ));
335  connect( observer, SIGNAL( diagramDataHidden(AbstractDiagram*) ),
336  SLOT( setNeedRebuild() ));
337  connect( observer, SIGNAL( diagramAttributesChanged(AbstractDiagram*) ),
338  SLOT( setNeedRebuild() ));
339  setNeedRebuild();
340  }
341 }
342 
344 {
345  int datasetBrushOffset = 0;
347  for ( int i = 0; i <diagrams.count(); i++ ) {
348  if ( diagrams.at( i ) == oldDiagram ) {
349  for ( int i = 0; i < oldDiagram->datasetBrushes().count(); i++ ) {
350  d->brushes.remove(datasetBrushOffset + i);
351  d->texts.remove(datasetBrushOffset + i);
352  }
353  for ( int i = 0; i < oldDiagram->datasetPens().count(); i++ ) {
354  d->pens.remove(datasetBrushOffset + i);
355  }
356  break;
357  }
358  datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
359  }
360 
361  if ( oldDiagram ) {
362  DiagramObserver *oldObs = d->findObserverForDiagram( oldDiagram );
363  if ( oldObs ) {
364  delete oldObs;
365  d->observers.removeAt( d->observers.indexOf( oldObs ) );
366  }
367  setNeedRebuild();
368  }
369 }
370 
372 {
373  // removeDiagram() may change the d->observers list. So, build up the list of
374  // diagrams to remove first and then remove them one by one.
376  for ( int i = 0; i < d->observers.size(); ++i ) {
377  diagrams.append( d->observers.at( i )->diagram() );
378  }
379  for ( int i = 0; i < diagrams.count(); ++i ) {
380  removeDiagram( diagrams[ i ] );
381  }
382 }
383 
385  AbstractDiagram* oldDiagram )
386 {
387  AbstractDiagram* old = oldDiagram;
388  if ( !d->observers.isEmpty() && !old ) {
389  old = d->observers.first()->diagram();
390  if ( !old ) {
391  d->observers.removeFirst(); // first entry had a 0 diagram
392  }
393  }
394  if ( old ) {
395  removeDiagram( old );
396  }
397  if ( newDiagram ) {
398  addDiagram( newDiagram );
399  }
400 }
401 
403 {
404  uint offset = 0;
405 
406  for ( int i = 0; i < d->observers.count(); ++i ) {
407  if ( d->observers.at(i)->diagram() == diagram ) {
408  return offset;
409  }
410  AbstractDiagram* diagram = d->observers.at(i)->diagram();
411  if ( !diagram->model() ) {
412  continue;
413  }
414  offset = offset + diagram->model()->columnCount();
415  }
416 
417  return offset;
418 }
419 
421 {
422  replaceDiagram( newDiagram );
423 }
424 
425 void Legend::resetDiagram( AbstractDiagram* oldDiagram )
426 {
427  removeDiagram( oldDiagram );
428 }
429 
430 void Legend::setVisible( bool visible )
431 {
432  // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
433  // on the visibility of the parent.
434  QWidget::setVisible( visible );
435  emitPositionChanged();
436 }
437 
438 void Legend::setNeedRebuild()
439 {
440  buildLegend();
441  sizeHint();
442 }
443 
445 {
446  if ( d->position == position ) {
447  return;
448  }
449  d->position = position;
450  emitPositionChanged();
451 }
452 
453 void Legend::emitPositionChanged()
454 {
455  emit positionChanged( this );
456  emit propertiesChanged();
457 }
458 
459 
461 {
462  return d->position;
463 }
464 
465 void Legend::setAlignment( Qt::Alignment alignment )
466 {
467  if ( d->alignment == alignment ) {
468  return;
469  }
470  d->alignment = alignment;
471  emitPositionChanged();
472 }
473 
474 Qt::Alignment Legend::alignment() const
475 {
476  return d->alignment;
477 }
478 
479 void Legend::setTextAlignment( Qt::Alignment alignment )
480 {
481  if ( d->textAlignment == alignment ) {
482  return;
483  }
484  d->textAlignment = alignment;
485  emitPositionChanged();
486 }
487 
488 Qt::Alignment Legend::textAlignment() const
489 {
490  return d->textAlignment;
491 }
492 
494 {
495  if ( d->legendLineSymbolAlignment == alignment ) {
496  return;
497  }
498  d->legendLineSymbolAlignment = alignment;
499  emitPositionChanged();
500 }
501 
502 Qt::Alignment Legend::legendSymbolAlignment() const
503 {
504  return d->legendLineSymbolAlignment ;
505 }
506 
507 void Legend::setFloatingPosition( const RelativePosition& relativePosition )
508 {
509  d->position = Position::Floating;
510  if ( d->relativePosition != relativePosition ) {
511  d->relativePosition = relativePosition;
512  emitPositionChanged();
513  }
514 }
515 
517 {
518  return d->relativePosition;
519 }
520 
521 void Legend::setOrientation( Qt::Orientation orientation )
522 {
523  if ( d->orientation == orientation ) {
524  return;
525  }
526  d->orientation = orientation;
527  setNeedRebuild();
528  emitPositionChanged();
529 }
530 
531 Qt::Orientation Legend::orientation() const
532 {
533  return d->orientation;
534 }
535 
536 void Legend::setSortOrder( Qt::SortOrder order )
537 {
538  if ( d->order == order ) {
539  return;
540  }
541  d->order = order;
542  setNeedRebuild();
543  emitPositionChanged();
544 }
545 
546 Qt::SortOrder Legend::sortOrder() const
547 {
548  return d->order;
549 }
550 
551 void Legend::setShowLines( bool legendShowLines )
552 {
553  if ( d->showLines == legendShowLines ) {
554  return;
555  }
556  d->showLines = legendShowLines;
557  setNeedRebuild();
558  emitPositionChanged();
559 }
560 
561 bool Legend::showLines() const
562 {
563  return d->showLines;
564 }
565 
567 {
568  d->useAutomaticMarkerSize = useAutomaticMarkerSize;
569  setNeedRebuild();
570  emitPositionChanged();
571 }
572 
574 {
575  return d->useAutomaticMarkerSize;
576 }
577 
584 {
585  if ( !d->texts.count() ) {
586  return;
587  }
588  d->texts.clear();
589  setNeedRebuild();
590 }
591 
592 void Legend::setText( uint dataset, const QString& text )
593 {
594  if ( d->texts[ dataset ] == text ) {
595  return;
596  }
597  d->texts[ dataset ] = text;
598  setNeedRebuild();
599 }
600 
601 QString Legend::text( uint dataset ) const
602 {
603  if ( d->texts.find( dataset ) != d->texts.end() ) {
604  return d->texts[ dataset ];
605  } else {
606  return d->modelLabels[ dataset ];
607  }
608 }
609 
611 {
612  return d->texts;
613 }
614 
615 void Legend::setColor( uint dataset, const QColor& color )
616 {
617  if ( d->brushes[ dataset ] != color ) {
618  d->brushes[ dataset ] = color;
619  setNeedRebuild();
620  update();
621  }
622 }
623 
624 void Legend::setBrush( uint dataset, const QBrush& brush )
625 {
626  if ( d->brushes[ dataset ] != brush ) {
627  d->brushes[ dataset ] = brush;
628  setNeedRebuild();
629  update();
630  }
631 }
632 
633 QBrush Legend::brush( uint dataset ) const
634 {
635  if ( d->brushes.contains( dataset ) ) {
636  return d->brushes[ dataset ];
637  } else {
638  return d->modelBrushes[ dataset ];
639  }
640 }
641 
643 {
644  return d->brushes;
645 }
646 
647 
649 {
650  bool changed = false;
651  QList<QBrush> datasetBrushes = diagram->datasetBrushes();
652  for ( int i = 0; i < datasetBrushes.count(); i++ ) {
653  if ( d->brushes[ i ] != datasetBrushes[ i ] ) {
654  d->brushes[ i ] = datasetBrushes[ i ];
655  changed = true;
656  }
657  }
658  if ( changed ) {
659  setNeedRebuild();
660  update();
661  }
662 }
663 
664 
665 void Legend::setPen( uint dataset, const QPen& pen )
666 {
667  if ( d->pens[dataset] == pen ) {
668  return;
669  }
670  d->pens[dataset] = pen;
671  setNeedRebuild();
672  update();
673 }
674 
675 QPen Legend::pen( uint dataset ) const
676 {
677  if ( d->pens.find( dataset ) != d->pens.end() ) {
678  return d->pens[ dataset ];
679  } else {
680  return d->modelPens[ dataset ];
681  }
682 }
683 
685 {
686  return d->pens;
687 }
688 
689 
691 {
692  if ( d->markerAttributes[dataset] == markerAttributes ) {
693  return;
694  }
695  d->markerAttributes[ dataset ] = markerAttributes;
696  setNeedRebuild();
697  update();
698 }
699 
701 {
702  if ( d->markerAttributes.find( dataset ) != d->markerAttributes.end() ) {
703  return d->markerAttributes[ dataset ];
704  } else if ( static_cast<uint>( d->modelMarkers.count() ) > dataset ) {
705  return d->modelMarkers[ dataset ];
706  } else {
707  return MarkerAttributes();
708  }
709 }
710 
712 {
713  return d->markerAttributes;
714 }
715 
716 
718 {
719  if ( d->textAttributes == a ) {
720  return;
721  }
722  d->textAttributes = a;
723  setNeedRebuild();
724 }
725 
727 {
728  return d->textAttributes;
729 }
730 
731 void Legend::setTitleText( const QString& text )
732 {
733  if ( d->titleText == text ) {
734  return;
735  }
736  d->titleText = text;
737  setNeedRebuild();
738 }
739 
740 QString Legend::titleText() const
741 {
742  return d->titleText;
743 }
744 
746 {
747  if ( d->titleTextAttributes == a ) {
748  return;
749  }
750  d->titleTextAttributes = a;
751  setNeedRebuild();
752 }
753 
755 {
756  return d->titleTextAttributes;
757 }
758 
760 {
761 #ifdef DEBUG_LEGEND_PAINT
762  qDebug() << "entering Legend::forceRebuild()";
763 #endif
764  buildLegend();
765 #ifdef DEBUG_LEGEND_PAINT
766  qDebug() << "leaving Legend::forceRebuild()";
767 #endif
768 }
769 
770 void Legend::setSpacing( uint space )
771 {
772  if ( d->spacing == space && d->layout->spacing() == int( space ) ) {
773  return;
774  }
775  d->spacing = space;
776  d->layout->setSpacing( space );
777  setNeedRebuild();
778 }
779 
780 uint Legend::spacing() const
781 {
782  return d->spacing;
783 }
784 
786 {
788  for ( int i = 0; i < pal.size(); i++ ) {
789  setBrush( i, pal.getBrush( i ) );
790  }
791 }
792 
794 {
796  for ( int i = 0; i < pal.size(); i++ ) {
797  setBrush( i, pal.getBrush( i ) );
798  }
799 }
800 
801 void Legend::setSubduedColors( bool ordered )
802 {
804  if ( ordered ) {
805  for ( int i = 0; i < pal.size(); i++ ) {
806  setBrush( i, pal.getBrush( i ) );
807  }
808  } else {
809  static const int s_subduedColorsCount = 18;
810  Q_ASSERT( pal.size() >= s_subduedColorsCount );
811  static const int order[ s_subduedColorsCount ] = {
812  0, 5, 10, 15, 2, 7, 12, 17, 4,
813  9, 14, 1, 6, 11, 16, 3, 8, 13
814  };
815  for ( int i = 0; i < s_subduedColorsCount; i++ ) {
816  setBrush( i, pal.getBrush( order[i] ) );
817  }
818  }
819 }
820 
821 void Legend::resizeEvent( QResizeEvent * event )
822 {
823  Q_UNUSED( event );
824 #ifdef DEBUG_LEGEND_PAINT
825  qDebug() << "Legend::resizeEvent() called";
826 #endif
827  forceRebuild();
828  sizeHint();
829  QTimer::singleShot( 0, this, SLOT(emitPositionChanged()) );
830 }
831 
832 void Legend::Private::fetchPaintOptions( Legend *q )
833 {
834  modelLabels.clear();
835  modelBrushes.clear();
836  modelPens.clear();
837  modelMarkers.clear();
838  // retrieve the diagrams' settings for all non-hidden datasets
839  for ( int i = 0; i < observers.size(); ++i ) {
840  const AbstractDiagram* diagram = observers.at( i )->diagram();
841  if ( !diagram ) {
842  continue;
843  }
844  const QStringList diagramLabels = diagram->datasetLabels();
845  const QList<QBrush> diagramBrushes = diagram->datasetBrushes();
846  const QList<QPen> diagramPens = diagram->datasetPens();
847  const QList<MarkerAttributes> diagramMarkers = diagram->datasetMarkers();
848 
849  const bool ascend = q->sortOrder() == Qt::AscendingOrder;
850  int dataset = ascend ? 0 : diagramLabels.count() - 1;
851  const int end = ascend ? diagramLabels.count() : -1;
852  for ( ; dataset != end; dataset += ascend ? 1 : -1 ) {
853  if ( diagram->isHidden( dataset ) || q->datasetIsHidden( dataset ) ) {
854  continue;
855  }
856  modelLabels += diagramLabels[ dataset ];
857  modelBrushes += diagramBrushes[ dataset ];
858  modelPens += diagramPens[ dataset ];
859  modelMarkers += diagramMarkers[ dataset ];
860  }
861  }
862 
863  Q_ASSERT( modelLabels.count() == modelBrushes.count() );
864 }
865 
866 QSizeF Legend::Private::markerSize( Legend *q, int dataset, qreal fontHeight ) const
867 {
868  QSizeF suppliedSize = q->markerAttributes( dataset ).markerSize();
869  if ( q->useAutomaticMarkerSize() || !suppliedSize.isValid() ) {
870  return QSizeF( fontHeight, fontHeight );
871  } else {
872  return suppliedSize;
873  }
874 }
875 
876 QSizeF Legend::Private::maxMarkerSize( Legend *q, qreal fontHeight ) const
877 {
878  QSizeF ret( 1.0, 1.0 );
879  if ( q->legendStyle() != LinesOnly ) {
880  for ( int dataset = 0; dataset < modelLabels.count(); ++dataset ) {
881  ret = ret.expandedTo( markerSize( q, dataset, fontHeight ) );
882  }
883  }
884  return ret;
885 }
886 
887 HDatasetItem::HDatasetItem()
888  : markerLine(0),
889  label(0),
890  separatorLine(0),
891  spacer(0)
892 {}
893 
895 {
896  while ( w ) {
897  if ( w->isTopLevel() ) {
898  // The null check has proved necessary during destruction of the Legend / Chart
899  if ( w->layout() ) {
900  w->layout()->update();
901  }
902  break;
903  } else {
904  w = qobject_cast< QWidget * >( w->parent() );
905  Q_ASSERT( w );
906  }
907  }
908 }
909 
910 void Legend::buildLegend()
911 {
912  /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
913  line between title and dataset items, row two for each item: line, marker, text label and separator
914  line in that order.
915  In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
916  and (second row) line, marker, text label each. */
917  d->destroyOldLayout();
918 
919  if ( orientation() == Qt::Vertical ) {
920  d->layout->setColumnStretch( 6, 1 );
921  } else {
922  d->layout->setColumnStretch( 6, 0 );
923  }
924 
925  d->fetchPaintOptions( this );
926 
927  const KDChartEnums::MeasureOrientation measureOrientation =
930 
931  // legend caption
932  if ( !titleText().isEmpty() && titleTextAttributes().isVisible() ) {
933  TextLayoutItem* titleItem =
935  measureOrientation, d->textAlignment );
936  titleItem->setParentWidget( this );
937 
938  d->paintItems << titleItem;
939  d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter );
940 
941  // The line between the title and the legend items, if any.
942  if ( showLines() && d->modelLabels.count() ) {
944  d->paintItems << lineItem;
945  d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter );
946  }
947  }
948 
949  qreal fontHeight = textAttributes().calculatedFontSize( referenceArea(), measureOrientation );
950  {
951  QFont tmpFont = textAttributes().font();
952  tmpFont.setPointSizeF( fontHeight );
954  fontHeight = QFontMetricsF( tmpFont, GlobalMeasureScaling::paintDevice() ).height();
955  } else {
956  fontHeight = QFontMetricsF( tmpFont ).height();
957  }
958  }
959 
960  const QSizeF maxMarkerSize = d->maxMarkerSize( this, fontHeight );
961 
962  // If we show a marker on a line, we paint it after 8 pixels
963  // of the line have been painted. This allows to see the line style
964  // at the right side of the marker without the line needing to
965  // be too long.
966  // (having the marker in the middle of the line would require longer lines)
967  const int lineLengthLeftOfMarker = 8;
968 
969  int maxLineLength = 18;
970  {
971  bool hasComplexPenStyle = false;
972  for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
973  const QPen pn = pen( dataset );
974  const Qt::PenStyle ps = pn.style();
975  if ( ps != Qt::NoPen ) {
976  maxLineLength = qMax( pn.width() * 18, maxLineLength );
977  if ( ps != Qt::SolidLine ) {
978  hasComplexPenStyle = true;
979  }
980  }
981  }
982  if ( hasComplexPenStyle && legendStyle() != LinesOnly ) {
983  maxLineLength += lineLengthLeftOfMarker + int( maxMarkerSize.width() );
984  }
985  }
986 
987  // for all datasets: add (line)marker items and text items to the layout;
988  // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical
989  for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
990  const int vLayoutRow = 2 + dataset * 2;
991  HDatasetItem dsItem;
992 
993  // It is possible to set the marker brush through markerAttributes as well as
994  // the dataset brush set in the diagram - the markerAttributes have higher precedence.
995  MarkerAttributes markerAttrs = markerAttributes( dataset );
996  markerAttrs.setMarkerSize( d->markerSize( this, dataset, fontHeight ) );
997  const QBrush markerBrush = markerAttrs.markerColor().isValid() ?
998  QBrush( markerAttrs.markerColor() ) : brush( dataset );
999 
1000  switch ( legendStyle() ) {
1001  case MarkersOnly:
1002  dsItem.markerLine = new MarkerLayoutItem( diagram(), markerAttrs, markerBrush,
1003  markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter );
1004  break;
1005  case LinesOnly:
1006  dsItem.markerLine = new LineLayoutItem( diagram(), maxLineLength, pen( dataset ),
1007  d->legendLineSymbolAlignment, Qt::AlignCenter );
1008  break;
1009  case MarkersAndLines:
1010  dsItem.markerLine = new LineWithMarkerLayoutItem(
1011  diagram(), maxLineLength, pen( dataset ), lineLengthLeftOfMarker, markerAttrs,
1012  markerBrush, markerAttrs.pen(), Qt::AlignCenter );
1013  break;
1014  default:
1015  Q_ASSERT( false );
1016  }
1017 
1018  dsItem.label = new TextLayoutItem( text( dataset ), textAttributes(), referenceArea(),
1019  measureOrientation, d->textAlignment );
1020  dsItem.label->setParentWidget( this );
1021 
1022  // horizontal layout is deferred to flowDatasetItems()
1023 
1024  if ( orientation() == Qt::Horizontal ) {
1025  d->hLayoutDatasets << dsItem;
1026  continue;
1027  }
1028 
1029  // (actual) vertical layout here
1030 
1031  if ( dsItem.markerLine ) {
1032  d->layout->addItem( dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter );
1033  d->paintItems << dsItem.markerLine;
1034  }
1035  d->layout->addItem( dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter );
1036  d->paintItems << dsItem.label;
1037 
1038  // horizontal separator line, only between items
1039  if ( showLines() && dataset != d->modelLabels.count() - 1 ) {
1041  d->layout->addItem( lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter );
1042  d->paintItems << lineItem;
1043  }
1044  }
1045 
1046  if ( orientation() == Qt::Horizontal ) {
1047  d->flowHDatasetItems( this );
1048  }
1049 
1050  // vertical line (only in vertical mode)
1051  if ( orientation() == Qt::Vertical && showLines() && d->modelLabels.count() ) {
1053  d->paintItems << lineItem;
1054  d->layout->addItem( lineItem, 2, 2, d->modelLabels.count() * 2, 1 );
1055  }
1056 
1057  updateToplevelLayout( this );
1058 
1059  emit propertiesChanged();
1060 #ifdef DEBUG_LEGEND_PAINT
1061  qDebug() << "leaving Legend::buildLegend()";
1062 #endif
1063 }
1064 
1065 int HDatasetItem::height() const
1066 {
1067  return qMax( markerLine->sizeHint().height(), label->sizeHint().height() );
1068 }
1069 
1070 void Legend::Private::reflowHDatasetItems( Legend *q )
1071 {
1072  if (hLayoutDatasets.isEmpty()) {
1073  return;
1074  }
1075 
1076  paintItems.clear();
1077  // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the
1078  // caption and line under the caption! Those are easily identified because they aren't QLayouts.
1079  for ( int i = layout->count() - 1; i >= 0; i-- ) {
1080  QLayoutItem *const item = layout->itemAt( i );
1081  QLayout *const hbox = item->layout();
1082  if ( !hbox ) {
1083  AbstractLayoutItem *alItem = dynamic_cast< AbstractLayoutItem * >( item );
1084  Q_ASSERT( alItem );
1085  paintItems << alItem;
1086  continue;
1087  }
1088  Q_ASSERT( dynamic_cast< QHBoxLayout * >( hbox ) );
1089  layout->takeAt( i );
1090  // detach children so they aren't deleted with the parent
1091  for ( int j = hbox->count() - 1; j >= 0; j-- ) {
1092  hbox->takeAt( j );
1093  }
1094  delete hbox;
1095  }
1096 
1097  flowHDatasetItems( q );
1098 }
1099 
1100 // this works pretty much like flow layout for text, and it is only applicable to dataset items
1101 // laid out horizontally
1102 void Legend::Private::flowHDatasetItems( Legend *q )
1103 {
1104  const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint()
1105 
1106  const int allowedWidth = q->areaGeometry().width();
1107 
1108  QHBoxLayout *currentLine = new QHBoxLayout;
1109  int mainLayoutRow = 1;
1110  layout->addItem( currentLine, mainLayoutRow++, /*column*/0,
1111  /*rowSpan*/1 , /*columnSpan*/5, Qt::AlignLeft | Qt::AlignVCenter );
1112 
1113  for ( int dataset = 0; dataset < hLayoutDatasets.size(); dataset++ ) {
1114  HDatasetItem *hdsItem = &hLayoutDatasets[ dataset ];
1115 
1116  bool spacerUsed = false;
1117  bool separatorUsed = false;
1118  if ( !currentLine->isEmpty() ) {
1119  const int separatorWidth = ( q->showLines() ? separatorLineWidth : 0 ) + q->spacing();
1120  const int payloadWidth = hdsItem->markerLine->sizeHint().width() +
1121  hdsItem->label->sizeHint().width();
1122  if ( currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth ) {
1123  // too wide, "line break"
1124 #ifdef DEBUG_LEGEND_PAINT
1125  qDebug() << Q_FUNC_INFO << "break" << mainLayoutRow
1126  << currentLine->sizeHint().width()
1127  << currentLine->sizeHint().width() + separatorWidth + payloadWidth
1128  << allowedWidth;
1129 #endif
1130  currentLine = new QHBoxLayout;
1131  layout->addItem( currentLine, mainLayoutRow++, /*column*/0,
1132  /*rowSpan*/1 , /*columnSpan*/5, Qt::AlignLeft | Qt::AlignVCenter );
1133  } else {
1134  // > 1 dataset item in line, put spacing and maybe a separator between them
1135  if ( !hdsItem->spacer ) {
1136  hdsItem->spacer = new QSpacerItem( q->spacing(), 1 );
1137  }
1138  currentLine->addItem( hdsItem->spacer );
1139  spacerUsed = true;
1140 
1141  if ( q->showLines() ) {
1142  if ( !hdsItem->separatorLine ) {
1143  hdsItem->separatorLine = new VerticalLineLayoutItem;
1144  }
1145  paintItems << hdsItem->separatorLine;
1146  currentLine->addItem( hdsItem->separatorLine );
1147  separatorUsed = true;
1148  }
1149  }
1150  }
1151  // those have no parents in the current layout, so they wouldn't get cleaned up otherwise
1152  if ( !spacerUsed ) {
1153  delete hdsItem->spacer;
1154  hdsItem->spacer = 0;
1155  }
1156  if ( !separatorUsed ) {
1157  delete hdsItem->separatorLine;
1158  hdsItem->separatorLine = 0;
1159  }
1160 
1161  currentLine->addItem( hdsItem->markerLine );
1162  paintItems << hdsItem->markerLine;
1163  currentLine->addItem( hdsItem->label );
1164  paintItems << hdsItem->label;
1165  }
1166 }
1167 
1169 {
1170  // this is better than using orientation() because, for layout purposes, we're not height-for-width
1171  // *yet* before buildLegend() has been called, and the layout logic might get upset if we say
1172  // something that will only be true in the future
1173  return !d->hLayoutDatasets.isEmpty();
1174 }
1175 
1176 int Legend::heightForWidth( int width ) const
1177 {
1178  if ( d->hLayoutDatasets.isEmpty() ) {
1179  return -1;
1180  }
1181 
1182  int ret = 0;
1183  // space for caption and line under caption (if any)
1184  for (int i = 0; i < 2; i++) {
1185  if ( QLayoutItem *item = d->layout->itemAtPosition( i, 0 ) ) {
1186  ret += item->sizeHint().height();
1187  }
1188  }
1189  const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint()
1190 
1191  int currentLineWidth = 0;
1192  int currentLineHeight = 0;
1193  Q_FOREACH( const HDatasetItem &hdsItem, d->hLayoutDatasets ) {
1194  const int payloadWidth = hdsItem.markerLine->sizeHint().width() +
1195  hdsItem.label->sizeHint().width();
1196  if ( !currentLineWidth ) {
1197  // first iteration
1198  currentLineWidth = payloadWidth;
1199  } else {
1200  const int separatorWidth = ( showLines() ? separatorLineWidth : 0 ) + spacing();
1201  currentLineWidth += separatorWidth + payloadWidth;
1202  if ( currentLineWidth > width ) {
1203  // too wide, "line break"
1204 #ifdef DEBUG_LEGEND_PAINT
1205  qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth
1206  << currentLineWidth + separatorWidth + payloadWidth
1207  << width;
1208 #endif
1209  ret += currentLineHeight + spacing();
1210  currentLineWidth = payloadWidth;
1211  currentLineHeight = 0;
1212  }
1213  }
1214  currentLineHeight = qMax( currentLineHeight, hdsItem.height() );
1215  }
1216  ret += currentLineHeight; // one less spacings than lines
1217  return ret;
1218 }
1219 
1220 void Legend::Private::destroyOldLayout()
1221 {
1222  // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items
1223  // (it isn't documented that QLayoutItems delete their children)
1224  for ( int i = layout->count() - 1; i >= 0; i-- ) {
1225  delete layout->takeAt( i );
1226  }
1227  Q_ASSERT( !layout->count() );
1228  hLayoutDatasets.clear();
1229  paintItems.clear();
1230 }
1231 
1233 {
1234  d->hiddenDatasets = hiddenDatasets;
1235 }
1236 
1238 {
1239  return d->hiddenDatasets;
1240 }
1241 
1242 void Legend::setDatasetHidden( uint dataset, bool hidden )
1243 {
1244  if ( hidden && !d->hiddenDatasets.contains( dataset ) ) {
1245  d->hiddenDatasets.append( dataset );
1246  } else if ( !hidden && d->hiddenDatasets.contains( dataset ) ) {
1247  d->hiddenDatasets.removeAll( dataset );
1248  }
1249 }
1250 
1251 bool Legend::datasetIsHidden( uint dataset ) const
1252 {
1253  return d->hiddenDatasets.contains( dataset );
1254 }
MeasureOrientation
Measure orientation mode: the way how the absolute value of a KDChart::Measure is determined during K...
Definition: KDChartEnums.h:290
Position position() const
Returns the position of a non-floating legend.
const QMap< uint, QBrush > brushes() const
A DiagramObserver watches the associated diagram for changes and deletion and emits corresponsing sig...
bool showLines() const
ConstDiagramList constDiagrams() const
const QMap< uint, QString > texts() const
Layout item showing a coloured line and a data point marker.
bool useAutomaticMarkerSize() const
void setAlignment(Qt::Alignment)
Specify the alignment of a non-floating legend.
A Palette is a set of brushes (or colors) to be used for painting data sets.
LegendStyle legendStyle() const
void setDiagram(KDChart::AbstractDiagram *newDiagram)
A convenience method doing the same as replaceDiagram( newDiagram, 0 );.
QBrush brush(uint dataset) const
void setOrientation(Qt::Orientation orientation)
Legend(QWidget *parent=0)
void setDatasetHidden(uint dataset, bool hidden)
void setReferenceArea(const QWidget *area)
Specifies the reference area for font size of title text, and for font size of the item texts...
MarkerAttributes markerAttributes(uint dataset) const
void setSortOrder(Qt::SortOrder order)
void setPosition(Position position)
Specify the position of a non-floating legend.
const QMap< uint, MarkerAttributes > markerAttributes() const
Qt::Orientation orientation() const
void setTextAlignment(Qt::Alignment)
Specify the alignment of the text elements within the legend.
void setTextAttributes(const TextAttributes &a)
void setPen(const QPen &pen)
Set the pen to use for rendering the text.
TextAttributes titleTextAttributes() const
void setMarkerSize(const QSizeF &size)
Normally you need to specify a valid QSizeF here, but for Legends you can use the invalid size QSizeF...
void setBrush(uint dataset, const QBrush &brush)
static const Palette & rainbowPalette()
void positionChanged(AbstractAreaWidget *)
A set of attributes for frames around items.
Layout item showing a text.
void replaceDiagram(KDChart::AbstractDiagram *newDiagram, KDChart::AbstractDiagram *oldDiagram=0)
Replaces the old diagram, or appends the new diagram, it there is none yet.
void setText(uint dataset, const QString &text)
Qt::Alignment legendSymbolAlignment() const
Returns the alignment used while drawing legend symbol(alignment of Legend::LinesOnly) within the leg...
virtual void setParentWidget(QWidget *widget)
Inform the item about its widget: This enables the item, to trigger that widget&#39;s update...
void setShowLines(bool legendShowLines)
QStringList datasetLabels() const
The set of dataset labels currently displayed, for use in legends, etc.
void needSizeHint() override
Call this to trigger an conditional re-building of the widget&#39;s internals.
QPen pen(uint dataset) const
uint datasetCount() const
static void updateToplevelLayout(QWidget *w)
void setBrushesFromDiagram(KDChart::AbstractDiagram *diagram)
void destroyedLegend(Legend *)
static QPaintDevice * paintDevice()
Return the paint device to use for calculating font metrics.
virtual Legend * clone() const
Creates an exact copy of this legend.
void addDiagram(KDChart::AbstractDiagram *newDiagram)
Add the given diagram to the legend.
void resetTexts()
Removes all legend texts that might have been set by setText.
AbstractDiagram defines the interface for diagram classes.
void resizeEvent(QResizeEvent *event) override
QString titleText() const
bool compare(const Legend *other) const
Returns true if both legends have the same settings.
#define d
void removeDiagrams()
Removes all diagrams from the legend&#39;s list of diagrams.
Layout item showing a data point marker.
bool datasetIsHidden(uint dataset) const
An area in the chart with a background, a frame, etc.
QSize sizeHint() const override
void setTitleTextAttributes(const TextAttributes &a)
void setMarkerAttributes(uint dataset, const MarkerAttributes &)
Note that any sizes specified via setMarkerAttributes are ignored, unless you disable the automatic s...
uint spacing() const
Class only listed here to document inheritance of some KDChart classes.
static const Palette & defaultPalette()
Provide access to the three builtin palettes, one with standard bright colors, one with more subdued ...
Layout item showing a horizontal line.
void setFontSize(const Measure &measure)
Set the size of the font used for rendering text.
Qt::SortOrder sortOrder() const
QBrush getBrush(int position) const
Query the palette for a brush at the specified position.
static const Position & NorthWest
Base class for all layout items of KD Chart.
Legend defines the interface for the legend drawing class.
Definition: KDChartLegend.h:55
QList< QBrush > datasetBrushes() const
The set of dataset brushes currently used, for use in legends, etc.
void setFont(const QFont &font)
Set the font to be used for rendering the text.
virtual void paint(QPainter *)=0
Layout item showing a vertial line.
void setTitleText(const QString &text)
bool compare(const AbstractAreaBase *other) const
Returns true if both areas have the same settings.
QList< QPen > datasetPens() const
The set of dataset pens currently used, for use in legends, etc.
void setLegendSymbolAlignment(Qt::Alignment)
Specify the alignment of the legend symbol( alignment of Legend::LinesOnly) within the legend...
void resizeLayout(const QSize &size) override
bool hasHeightForWidth() const
Stores the absolute target points of a Position.
void forceRebuild() override
Call this to trigger an unconditional re-building of the widget&#39;s internals.
void paint(QPainter *painter) override
Overwrite this to paint the inner contents of your widget.
TextAttributes textAttributes() const
QSize minimumSizeHint() const override
void removeDiagram(KDChart::AbstractDiagram *oldDiagram)
Removes the diagram from the legend&#39;s list of diagrams.
A set of attributes controlling the appearance of data set markers.
static const Position & NorthEast
const RelativePosition floatingPosition() const
Returns the position of a floating legend.
KDChart::AbstractDiagram * diagram() const
The first diagram of the legend or 0 if there was none added to the legend.
const QList< uint > hiddenDatasets() const
void setLegendStyle(LegendStyle style)
Measure is used to specify relative and absolute sizes in KDChart, e.g.
DiagramList diagrams() const
The list of all diagrams associated with the legend.
Layout item showing a coloured line.
int heightForWidth(int width) const override
void setHiddenDatasets(const QList< uint > hiddenDatasets)
Sets a list of datasets that are to be hidden in the legend.
Defines relative position information: reference area, position in this area (reference position)...
Qt::Alignment textAlignment() const
Returns the alignment used while rendering text elements within the legend.
Class only listed here to document inheritance of some KDChart classes.
void setMinimalFontSize(const Measure &measure)
Set the minimal size of the font used for rendering text.
void setVisible(bool visible) override
Defines a position, using compass terminology.
void setPen(uint dataset, const QPen &pen)
bool isHidden() const
Retrieve the hidden status specified globally.
const AbstractDiagram * diagram() const
void setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)
This option is on by default, it means that Marker sizes in the Legend will be the same as the font h...
static const Palette & subduedPalette()
uint dataSetOffset(KDChart::AbstractDiagram *diagram)
Returns the offset of the first dataset of diagram.
QList< MarkerAttributes > datasetMarkers() const
The set of dataset markers currently used, for use in legends, etc.
const QWidget * referenceArea() const
Returns the reference area, that is used for font size of title text, and for font size of the item t...
Qt::Alignment alignment() const
Returns the alignment of a non-floating legend.
Class only listed here to document inheritance of some KDChart classes.
void setSpacing(uint space)
static const Position & Floating
FrameAttributes frameAttributes() const
qreal calculatedFontSize(const QSizeF &referenceSize, KDChartEnums::MeasureOrientation autoReferenceOrientation) const
Returns the font size that is used at drawing time.
void setFrameAttributes(const FrameAttributes &a)
~Legend() override
void setFloatingPosition(const RelativePosition &relativePosition)
Specify the position and alignment of a floating legend.
A set of text attributes.
const QMap< uint, QPen > pens() const
void propertiesChanged()
Emitted upon change of a property of the Legend or any of its components.
void setColor(uint dataset, const QColor &color)
Note: there is no color() getter method, since setColor just sets a QBrush with the respective color...
QString text(uint dataset) const
void setSubduedColors(bool ordered=false)

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/