KChart

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

KDE's Doxygen guidelines are available online.