QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgslegendrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslegendrenderer.cpp
3 --------------------------------------
4 Date : July 2014
5 Copyright : (C) 2014 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslegendrenderer.h"
17
18#include "qgslayertree.h"
19#include "qgslayertreemodel.h"
21#include "qgslegendstyle.h"
22#include "qgsmaplayerlegend.h"
23#include "qgssymbol.h"
24#include "qgsrendercontext.h"
25#include "qgsvectorlayer.h"
27
28#include <QJsonObject>
29#include <QPainter>
30
31
32
34 : mLegendModel( legendModel )
35 , mSettings( settings )
36{
37}
38
40{
41 std::unique_ptr< QgsRenderContext > tmpContext;
42
43 if ( !renderContext )
44 {
45 // QGIS 4.0 - make render context mandatory
47 tmpContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( nullptr ) ) );
48 tmpContext->setRendererScale( mSettings.mapScale() );
49 tmpContext->setMapToPixel( QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * tmpContext->scaleFactor() ) ) );
50 renderContext = tmpContext.get();
52 }
53
54 QgsScopedRenderContextPainterSwap nullPainterSwap( *renderContext, nullptr );
55 return paintAndDetermineSize( *renderContext );
56}
57
58void QgsLegendRenderer::drawLegend( QPainter *painter )
59{
62 QgsScopedRenderContextScaleToMm scaleToMm( context );
63
64 context.setRendererScale( mSettings.mapScale() );
65 context.setMapToPixel( QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * context.scaleFactor() ) ) );
67
68 paintAndDetermineSize( context );
69}
70
72{
73 QJsonObject json;
74
75 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
76 if ( !rootGroup )
77 return json;
78
79 json = exportLegendToJson( context, rootGroup );
80 json[QStringLiteral( "title" )] = mSettings.title();
81 return json;
82}
83
84QJsonObject QgsLegendRenderer::exportLegendToJson( const QgsRenderContext &context, QgsLayerTreeGroup *nodeGroup )
85{
86 QJsonObject json;
87 QJsonArray nodes;
88 const QList<QgsLayerTreeNode *> childNodes = nodeGroup->children();
89 for ( QgsLayerTreeNode *node : childNodes )
90 {
91 if ( QgsLayerTree::isGroup( node ) )
92 {
93 QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
94 const QModelIndex idx = mLegendModel->node2index( nodeGroup );
95 const QString text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
96
97 QJsonObject group = exportLegendToJson( context, nodeGroup );
98 group[ QStringLiteral( "type" ) ] = QStringLiteral( "group" );
99 group[ QStringLiteral( "title" ) ] = text;
100 nodes.append( group );
101 }
102 else if ( QgsLayerTree::isLayer( node ) )
103 {
104 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
105
106 QString text;
107 if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
108 {
109 const QModelIndex idx = mLegendModel->node2index( nodeLayer );
110 text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
111 }
112
113 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
114
115 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
116 continue;
117
118 if ( legendNodes.count() == 1 )
119 {
120 QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
121 group[ QStringLiteral( "type" ) ] = QStringLiteral( "layer" );
122 nodes.append( group );
123 }
124 else if ( legendNodes.count() > 1 )
125 {
126 QJsonObject group;
127 group[ QStringLiteral( "type" ) ] = QStringLiteral( "layer" );
128 group[ QStringLiteral( "title" ) ] = text;
129
130 QJsonArray symbols;
131 for ( int j = 0; j < legendNodes.count(); j++ )
132 {
133 QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
134 QJsonObject symbol = legendNode->exportToJson( mSettings, context );
135 symbols.append( symbol );
136 }
137 group[ QStringLiteral( "symbols" ) ] = symbols;
138
139 nodes.append( group );
140 }
141 }
142 }
143
144 json[QStringLiteral( "nodes" )] = nodes;
145 return json;
146}
147
148QSizeF QgsLegendRenderer::paintAndDetermineSize( QgsRenderContext &context )
149{
150 QSizeF size( 0, 0 );
151 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
152 if ( !rootGroup )
153 return size;
154
155 // temporarily remove painter from context -- we don't need to actually draw anything yet. But we DO need
156 // to send the full render context so that an expression context is available during the size calculation
157 QgsScopedRenderContextPainterSwap noPainter( context, nullptr );
158
159 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
160
161 const int columnCount = setColumns( componentGroups );
162
163 QMap< int, double > maxColumnWidths;
164 qreal maxEqualColumnWidth = 0;
165 // another iteration -- this one is required to calculate the maximum item width for each
166 // column. Unfortunately, we can't trust the component group widths at this stage, as they are minimal widths
167 // only. When actually rendering a symbol node, the text is aligned according to the WIDEST
168 // symbol in a column. So that means we can't possibly determine the exact size of legend components
169 // until now. BUUUUUUUUUUUUT. Because everything sucks, we can't even start the actual render of items
170 // at the same time we calculate this -- legend items REQUIRE the REAL width of the columns in order to
171 // correctly align right or center-aligned symbols/text. Bah -- A triple iteration it is!
172 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
173 {
174 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
175 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
176 maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
177 }
178
179 if ( columnCount == 1 )
180 {
181 // single column - use the full available width
182 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
183 maxColumnWidths[ 0 ] = maxEqualColumnWidth;
184 }
185
186 //calculate size of title
187 QSizeF titleSize = drawTitle( context, 0 );
188 //add title margin to size of title text
189 titleSize.rwidth() += mSettings.boxSpace() * 2.0;
190 double columnTop = mSettings.boxSpace() + titleSize.height() + mSettings.style( QgsLegendStyle::Title ).margin( QgsLegendStyle::Bottom );
191
192 noPainter.reset();
193
194 bool firstInColumn = true;
195 double columnMaxHeight = 0;
196 qreal columnWidth = 0;
197 int column = -1;
198 ColumnContext columnContext;
199 columnContext.left = mSettings.boxSpace();
200 columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
201 double currentY = columnTop;
202
203 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
204 {
205 if ( group.column > column )
206 {
207 // Switch to next column
208 columnContext.left = group.column > 0 ? columnContext.right + mSettings.columnSpace() : mSettings.boxSpace();
209 columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
210 columnContext.right = columnContext.left + columnWidth;
211 currentY = columnTop;
212 column++;
213 firstInColumn = true;
214 }
215 if ( !firstInColumn )
216 {
217 currentY += spaceAboveGroup( group );
218 }
219
220 drawGroup( group, context, columnContext, currentY );
221
222 currentY += group.size.height();
223 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
224
225 firstInColumn = false;
226 }
227 const double totalWidth = columnContext.right + mSettings.boxSpace();
228
229 size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
230 size.rwidth() = totalWidth;
231 if ( !mSettings.title().isEmpty() )
232 {
233 size.rwidth() = std::max( titleSize.width(), size.width() );
234 }
235
236 // override the size if it was set by the user
237 if ( mLegendSize.isValid() )
238 {
239 qreal w = std::max( size.width(), mLegendSize.width() );
240 qreal h = std::max( size.height(), mLegendSize.height() );
241 size = QSizeF( w, h );
242 }
243
244 // Now we have set the correct total item width and can draw the title centered
245 if ( !mSettings.title().isEmpty() )
246 {
247 drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
248 }
249
250 return size;
251}
252
253void QgsLegendRenderer::widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, const double legendWidth, double &textBoxWidth, double &textBoxLeft ) const
254{
255 switch ( halignment )
256 {
257 default:
258 textBoxLeft = mSettings.boxSpace();
259 textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
260 break;
261
262 case Qt::AlignHCenter:
263 {
264 // not sure on this logic, I just moved it -- don't blame me for it being totally obscure!
265 const double centerX = legendWidth / 2;
266 textBoxWidth = ( std::min( static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
267 textBoxLeft = centerX - textBoxWidth / 2.;
268 break;
269 }
270 }
271}
272
273QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList( QgsLayerTreeGroup *parentGroup, QgsRenderContext &context, double indent )
274{
275 QList<LegendComponentGroup> componentGroups;
276
277 if ( !parentGroup )
278 return componentGroups;
279
280 const QList<QgsLayerTreeNode *> childNodes = parentGroup->children();
281 for ( QgsLayerTreeNode *node : childNodes )
282 {
283 if ( QgsLayerTree::isGroup( node ) )
284 {
285 QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
286 QString style = node->customProperty( QStringLiteral( "legend/title-style" ) ).toString();
287 // Update the required indent for the group/subgroup items, starting from the indent accumulated from parent groups
288 double newIndent = indent;
289 if ( style == QLatin1String( "subgroup" ) )
290 {
291 newIndent += mSettings.style( QgsLegendStyle::Subgroup ).indent( );
292 }
293 else
294 {
295 newIndent += mSettings.style( QgsLegendStyle::Group ).indent( );
296 }
297
298 // Group subitems
299 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
300
301 bool hasSubItems = !subgroups.empty();
302
303 if ( nodeLegendStyle( nodeGroup ) != QgsLegendStyle::Hidden )
304 {
305 LegendComponent component;
306 component.item = node;
307 component.indent = newIndent;
308 component.size = drawGroupTitle( nodeGroup, context );
309
310 if ( !subgroups.isEmpty() )
311 {
312 // Add internal space between this group title and the next component
313 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
314 // Prepend this group title to the first group
315 subgroups[0].components.prepend( component );
316 subgroups[0].size.rheight() += component.size.height();
317 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
318 if ( nodeGroup->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() )
319 subgroups[0].placeColumnBreakBeforeGroup = true;
320 }
321 else
322 {
323 // no subitems, create new group
324 LegendComponentGroup group;
325 group.placeColumnBreakBeforeGroup = nodeGroup->customProperty( QStringLiteral( "legend/column-break" ) ).toInt();
326 group.components.append( component );
327 group.size.rwidth() += component.size.width();
328 group.size.rheight() += component.size.height();
329 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
330 subgroups.append( group );
331 }
332 }
333
334 if ( hasSubItems ) //leave away groups without content
335 {
336 componentGroups.append( subgroups );
337 }
338
339 }
340 else if ( QgsLayerTree::isLayer( node ) )
341 {
342 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
343
344 bool allowColumnSplit = false;
345 switch ( nodeLayer->legendSplitBehavior() )
346 {
348 allowColumnSplit = mSettings.splitLayer();
349 break;
351 allowColumnSplit = true;
352 break;
354 allowColumnSplit = false;
355 break;
356 }
357
358 LegendComponentGroup group;
359 group.placeColumnBreakBeforeGroup = nodeLayer->customProperty( QStringLiteral( "legend/column-break" ) ).toInt();
360
361 if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
362 {
363 LegendComponent component;
364 component.item = node;
365 component.size = drawLayerTitle( nodeLayer, context );
366 component.indent = indent;
367 group.components.append( component );
368 group.size.rwidth() = component.size.width();
369 group.size.rheight() = component.size.height();
370 }
371
372 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
373
374 // workaround for the issue that "filtering by map" does not remove layer nodes that have no symbols present
375 // on the map. We explicitly skip such layers here. In future ideally that should be handled directly
376 // in the layer tree model
377 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
378 continue;
379
380 QList<LegendComponentGroup> layerGroups;
381 layerGroups.reserve( legendNodes.count() );
382
383 bool groupIsLayerGroup = true;
384
385 for ( int j = 0; j < legendNodes.count(); j++ )
386 {
387 QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
388
389 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
390
391 const bool forceBreak = legendNode->columnBreak();
392
393 if ( !allowColumnSplit || j == 0 )
394 {
395 if ( forceBreak )
396 {
397 if ( groupIsLayerGroup )
398 layerGroups.prepend( group );
399 else
400 layerGroups.append( group );
401
402 group = LegendComponentGroup();
403 group.placeColumnBreakBeforeGroup = true;
404 groupIsLayerGroup = false;
405 }
406
407 // append to layer group
408 // the width is not correct at this moment, we must align all symbol labels
409 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
410 // Add symbol space only if there is already title or another item above
411 if ( !group.components.isEmpty() )
412 {
413 // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
414 group.size.rheight() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
415 }
416 group.size.rheight() += symbolComponent.size.height();
417 symbolComponent.indent = indent;
418 group.components.append( symbolComponent );
419 }
420 else
421 {
422 if ( group.size.height() > 0 )
423 {
424 if ( groupIsLayerGroup )
425 layerGroups.prepend( group );
426 else
427 layerGroups.append( group );
428 group = LegendComponentGroup();
429 groupIsLayerGroup = false;
430 }
431 LegendComponentGroup symbolGroup;
432 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
433 symbolComponent.indent = indent;
434 symbolGroup.components.append( symbolComponent );
435 symbolGroup.size.rwidth() = symbolComponent.size.width();
436 symbolGroup.size.rheight() = symbolComponent.size.height();
437 layerGroups.append( symbolGroup );
438 }
439 }
440 if ( group.size.height() > 0 )
441 {
442 if ( groupIsLayerGroup )
443 layerGroups.prepend( group );
444 else
445 layerGroups.append( group );
446 }
447 componentGroups.append( layerGroups );
448 }
449 }
450
451 return componentGroups;
452}
453
454
455int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
456{
457 // Divide groups to columns
458 double totalHeight = 0;
459 qreal maxGroupHeight = 0;
460 int forcedColumnBreaks = 0;
461 double totalSpaceAboveGroups = 0;
462
463 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
464 {
465 const double topMargin = spaceAboveGroup( group );
466 totalHeight += topMargin;
467 totalSpaceAboveGroups += topMargin;
468
469 const double groupHeight = group.size.height();
470 totalHeight += groupHeight;
471 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
472
473 if ( group.placeColumnBreakBeforeGroup )
474 forcedColumnBreaks++;
475 }
476 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
477 double averageGroupHeight = totalGroupHeight / componentGroups.size();
478
479 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
480 return 0;
481
482 // the target number of columns allowed is dictated by the number of forced column
483 // breaks OR the manually set column count (whichever is greater!)
484 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
485 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
486
487 // We know height of each group and we have to split them into columns
488 // minimizing max column height. It is sort of bin packing problem, NP-hard.
489 // We are using simple heuristic, brute fore appeared to be to slow,
490 // the number of combinations is N = n!/(k!*(n-k)!) where n = groupCount-1
491 // and k = columnsCount-1
492 double maxColumnHeight = 0;
493 int currentColumn = 0;
494 int currentColumnGroupCount = 0; // number of groups in current column
495 double currentColumnHeight = 0;
496 int autoPlacedBreaks = 0;
497
498 // Calculate the expected average space between items
499 double averageSpaceAboveGroups = 0;
500 if ( componentGroups.size() > targetNumberColumns )
501 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
502
503 double totalRemainingGroupHeight = totalGroupHeight;
504 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
505 for ( int i = 0; i < componentGroups.size(); i++ )
506 {
507 const LegendComponentGroup &group = componentGroups.at( i );
508 const double currentGroupHeight = group.size.height();
509 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
510
511 totalRemainingGroupHeight -= currentGroupHeight;
512 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
513
514 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
515 if ( currentColumnGroupCount > 0 )
516 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
517 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
518
519 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
520 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
521 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
522
523 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
524 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
525 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight
526 + totalRemainingSpaceAboveGroups
527 - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
528
529 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded
530 + estimatedRemainingTotalHeightAfterThisGroup;
531
532 // Recalc average height for remaining columns including current
533 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
534
535 // Round up to the next full number of groups to put in one column
536 // This ensures that earlier columns contain more elements than later columns
537 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
538
539 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
540
541 bool canCreateNewColumn = ( currentColumnGroupCount > 0 ) // do not leave empty column
542 && ( currentColumn < targetNumberColumns - 1 ) // must not exceed max number of columns
543 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
544
545 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne // current group height is greater than expected group height
546 && currentColumnGroupCount > 0 // do not leave empty column
547 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight // no sense to make smaller columns than max group height
548 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight; // no sense to make smaller columns than max column already created
549
550 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
551 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
552
553 // also should create a new column if the number of items left < number of columns left
554 // in this case we should spread the remaining items out over the remaining columns
555 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
556
557 if ( canCreateNewColumn && shouldCreateNewColumn )
558 {
559 // New column
560 currentColumn++;
561 if ( !group.placeColumnBreakBeforeGroup )
562 autoPlacedBreaks++;
563 currentColumnGroupCount = 0;
564 currentColumnHeight = group.size.height();
565 }
566 else
567 {
568 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
569 }
570 componentGroups[i].column = currentColumn;
571 currentColumnGroupCount++;
572 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
573 }
574
575 auto refineColumns = [&componentGroups, this]() -> bool
576 {
577 QHash< int, double > columnHeights;
578 QHash< int, int > columnGroupCounts;
579 double currentColumnHeight = 0;
580 int currentColumn = -1;
581 int columnCount = 0;
582 int groupCount = 0;
583 double maxCurrentColumnHeight = 0;
584 for ( int i = 0; i < componentGroups.size(); i++ )
585 {
586 const LegendComponentGroup &group = componentGroups.at( i );
587 if ( group.column != currentColumn )
588 {
589 if ( currentColumn >= 0 )
590 {
591 columnHeights.insert( currentColumn, currentColumnHeight );
592 columnGroupCounts.insert( currentColumn, groupCount );
593 }
594
595 currentColumn = group.column;
596 currentColumnHeight = 0;
597 groupCount = 0;
598 columnCount = std::max( columnCount, currentColumn + 1 );
599 }
600
601 const double spaceAbove = spaceAboveGroup( group );
602 currentColumnHeight += spaceAbove + group.size.height();
603 groupCount++;
604 }
605 columnHeights.insert( currentColumn, currentColumnHeight );
606 columnGroupCounts.insert( currentColumn, groupCount );
607
608 double totalColumnHeights = 0;
609 for ( int i = 0; i < columnCount; ++ i )
610 {
611 totalColumnHeights += columnHeights[i];
612 maxCurrentColumnHeight = std::max( maxCurrentColumnHeight, columnHeights[i] );
613 }
614
615 const double averageColumnHeight = totalColumnHeights / columnCount;
616
617 bool changed = false;
618 int nextCandidateColumnForShift = 1;
619 for ( int i = 0; i < componentGroups.size(); i++ )
620 {
621 LegendComponentGroup &group = componentGroups[ i ];
622 if ( group.column < nextCandidateColumnForShift )
623 continue;
624
625 // try shifting item to previous group
626 const bool canShift = !group.placeColumnBreakBeforeGroup
627 && columnGroupCounts[ group.column ] >= 2;
628
629 if ( canShift
630 && columnHeights[ group.column - 1 ] < averageColumnHeight
631 && ( columnHeights[ group.column - 1 ] + group.size.height() ) * 0.9 < maxCurrentColumnHeight
632 )
633 {
634 group.column -= 1;
635 columnHeights[ group.column ] += group.size.height() + spaceAboveGroup( group );
636 columnGroupCounts[ group.column ]++;
637 columnHeights[ group.column + 1 ] -= group.size.height();
638 columnGroupCounts[ group.column + 1]--;
639 changed = true;
640 }
641 else
642 {
643 nextCandidateColumnForShift = group.column + 1;
644 }
645 }
646 return changed;
647 };
648
649 bool wasRefined = true;
650 int iterations = 0;
651 while ( wasRefined && iterations < 2 )
652 {
653 wasRefined = refineColumns();
654 iterations++;
655 }
656
657 // Align labels of symbols for each layer/column to the same labelXOffset
658 QMap<QString, qreal> maxSymbolWidth;
659 for ( int i = 0; i < componentGroups.size(); i++ )
660 {
661 LegendComponentGroup &group = componentGroups[i];
662 for ( int j = 0; j < group.components.size(); j++ )
663 {
664 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
665 {
666 QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
667 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
668 }
669 }
670 }
671 for ( int i = 0; i < componentGroups.size(); i++ )
672 {
673 LegendComponentGroup &group = componentGroups[i];
674 for ( int j = 0; j < group.components.size(); j++ )
675 {
676 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
677 {
678 QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
679 double space = mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right ) +
681 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
682 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
683 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
684 }
685 }
686 }
687 return targetNumberColumns;
688}
689
690QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext &context, double top, Qt::AlignmentFlag halignment, double legendWidth )
691{
692 QSizeF size( 0, 0 );
693 if ( mSettings.title().isEmpty() )
694 {
695 return size;
696 }
697
698 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
699 double y = top;
700
701 if ( auto *lPainter = context.painter() )
702 {
703 lPainter->setPen( mSettings.fontColor() );
704 }
705
706 //calculate width and left pos of rectangle to draw text into
707 double textBoxWidth;
708 double textBoxLeft;
709 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
710
711 QFont titleFont = mSettings.style( QgsLegendStyle::Title ).font();
712
713 for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
714 {
715 //last word is not drawn if rectangle width is exactly text width, so add 1
716 //TODO - correctly calculate size of italicized text, since QFontMetrics does not
717 qreal width = mSettings.textWidthMillimeters( titleFont, *titlePart ) + 1;
718 qreal height = mSettings.fontAscentMillimeters( titleFont ) + mSettings.fontDescentMillimeters( titleFont );
719
720 QRectF r( textBoxLeft, y, textBoxWidth, height );
721
722 if ( context.painter() )
723 {
724 mSettings.drawText( context.painter(), r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
725 }
726
727 //update max width of title
728 size.rwidth() = std::max( width, size.rwidth() );
729
730 y += height;
731 if ( titlePart != ( lines.end() - 1 ) )
732 {
733 y += mSettings.lineSpacing();
734 }
735 }
736 size.rheight() = y - top;
737
738 return size;
739}
740
741
742double QgsLegendRenderer::spaceAboveGroup( const LegendComponentGroup &group )
743{
744 if ( group.components.isEmpty() ) return 0;
745
746 LegendComponent component = group.components.first();
747
748 if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
749 {
750 return mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Top );
751 }
752 else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
753 {
754 return mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Top );
755 }
756 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
757 {
758 // TODO: use Symbol or SymbolLabel Top margin
760 }
761
762 return 0;
763}
764
765QSizeF QgsLegendRenderer::drawGroup( const LegendComponentGroup &group, QgsRenderContext &context, ColumnContext columnContext, double top )
766{
767 bool first = true;
768 QSizeF size = QSizeF( group.size );
769 double currentY = top;
770 for ( const LegendComponent &component : std::as_const( group.components ) )
771 {
772 if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
773 {
774 QgsLegendStyle::Style s = nodeLegendStyle( groupItem );
775 if ( s != QgsLegendStyle::Hidden )
776 {
777 if ( !first )
778 {
779 currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
780 }
781 QSizeF groupSize;
782 ColumnContext columnContextForItem = columnContext;
783 double indentWidth = component.indent;
784 if ( s == QgsLegendStyle::Subgroup )
785 {
786 // Remove indent - the subgroup items should be indented, not the subgroup title
787 indentWidth -= mSettings.style( QgsLegendStyle::Subgroup ).indent( );
788 }
789 else
790 {
791 // Remove indent - the group items should be indented, not the group title
792 indentWidth -= mSettings.style( QgsLegendStyle::Group ).indent( );
793 }
794 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignLeft )
795 {
796 columnContextForItem.left += indentWidth;
797 }
798 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignRight )
799 {
800 columnContextForItem.right -= indentWidth;
801 }
802 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
803 size.rwidth() = std::max( groupSize.width(), size.width() );
804 }
805 }
806 else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
807 {
808 QgsLegendStyle::Style s = nodeLegendStyle( layerItem );
809 if ( s != QgsLegendStyle::Hidden )
810 {
811 if ( !first )
812 {
813 currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
814 }
815 QSizeF subGroupSize;
816
817 ColumnContext columnContextForItem = columnContext;
818 double indentWidth = component.indent;
819 columnContextForItem.left += indentWidth;
820 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
821 size.rwidth() = std::max( subGroupSize.width(), size.width() );
822 }
823 }
824 else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
825 {
826 if ( !first )
827 {
828 currentY += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
829 }
830
831 ColumnContext columnContextForItem = columnContext;
832 double indentWidth = 0;
833 indentWidth = component.indent;
834 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignLeft )
835 {
836 columnContextForItem.left += indentWidth;
837 }
838 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignRight )
839 {
840 columnContextForItem.right -= indentWidth;
841 }
842
843 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
844 // expand width, it may be wider because of label offsets
845 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
846 }
847 currentY += component.size.height();
848 first = false;
849 }
850 return size;
851}
852
853QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext &context, ColumnContext columnContext, double top, double maxSiblingSymbolWidth )
854{
856 ctx.context = &context;
857
858 // add a layer expression context scope
859 QgsExpressionContextScope *layerScope = nullptr;
860 if ( symbolItem->layerNode()->layer() )
861 {
862 layerScope = QgsExpressionContextUtils::layerScope( symbolItem->layerNode()->layer() );
863 context.expressionContext().appendScope( layerScope );
864 }
865
866 ctx.painter = context.painter();
868 ctx.point = QPointF( columnContext.left, top );
869 ctx.labelXOffset = maxSiblingSymbolWidth;
871
872 ctx.top = top;
873
874 ctx.columnLeft = columnContext.left;
875 ctx.columnRight = columnContext.right;
876
877 switch ( mSettings.symbolAlignment() )
878 {
879 case Qt::AlignLeft:
880 default:
882 break;
883
884 case Qt::AlignRight:
886 break;
887 }
888
889 ctx.maxSiblingSymbolWidth = maxSiblingSymbolWidth;
890
891 if ( const QgsSymbolLegendNode *symbolNode = dynamic_cast< const QgsSymbolLegendNode * >( symbolItem ) )
892 ctx.patchShape = symbolNode->patchShape();
893
894 ctx.patchSize = symbolItem->userPatchSize();
895
896 QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->draw( mSettings, &ctx );
897
898 if ( layerScope )
899 delete context.expressionContext().popScope();
900
901 LegendComponent component;
902 component.item = symbolItem;
903 component.symbolSize = im.symbolSize;
904 component.labelSize = im.labelSize;
905 //QgsDebugMsg( QStringLiteral( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
906 // NOTE -- we hard code left/right margins below, because those are the only ones exposed for use currently.
907 // ideally we could (should?) expose all these margins as settings, and then adapt the below to respect the current symbol/text alignment
908 // and consider the correct margin sides...
909 double width = std::max( static_cast< double >( im.symbolSize.width() ), maxSiblingSymbolWidth )
913 + im.labelSize.width();
914
915 double height = std::max( im.symbolSize.height(), im.labelSize.height() );
916 component.size = QSizeF( width, height );
917 return component;
918}
919
920QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext &context, ColumnContext columnContext, double top )
921{
922 QSizeF size( 0, 0 );
923 QModelIndex idx = mLegendModel->node2index( nodeLayer );
924 QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
925 //Let the user omit the layer title item by having an empty layer title string
926 if ( titleString.isEmpty() )
927 return size;
928
929 double y = top;
930
931 if ( auto *lPainter = context.painter() )
932 lPainter->setPen( mSettings.layerFontColor() );
933
934 QFont layerFont = mSettings.style( nodeLegendStyle( nodeLayer ) ).font();
935
936 QgsExpressionContextScope *layerScope = nullptr;
937 if ( nodeLayer->layer() )
938 {
939 layerScope = QgsExpressionContextUtils::layerScope( nodeLayer->layer() );
940 context.expressionContext().appendScope( layerScope );
941 }
942
943 const QStringList lines = mSettings.evaluateItemText( titleString, context.expressionContext() );
944 int i = 0;
945
946 const double sideMargin = mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Left );
947 for ( QStringList::ConstIterator layerItemPart = lines.constBegin(); layerItemPart != lines.constEnd(); ++layerItemPart )
948 {
949 y += mSettings.fontAscentMillimeters( layerFont );
950 if ( QPainter *destPainter = context.painter() )
951 {
952 double x = columnContext.left + sideMargin;
953 if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() != Qt::AlignLeft )
954 {
955 const double labelWidth = mSettings.textWidthMillimeters( layerFont, *layerItemPart );
956 if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignRight )
957 x = columnContext.right - labelWidth - sideMargin;
958 else if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter )
959 x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
960 }
961 mSettings.drawText( destPainter, x, y, *layerItemPart, layerFont );
962 }
963 qreal width = mSettings.textWidthMillimeters( layerFont, *layerItemPart ) + sideMargin *
964 ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
965 size.rwidth() = std::max( width, size.width() );
966 if ( layerItemPart != ( lines.end() - 1 ) )
967 {
968 y += mSettings.lineSpacing();
969 }
970 i++;
971 }
972 size.rheight() = y - top;
973 size.rheight() += mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Side::Bottom );
974
975 if ( layerScope )
976 delete context.expressionContext().popScope();
977
978 return size;
979}
980
981QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext &context, ColumnContext columnContext, double top )
982{
983 QSizeF size( 0, 0 );
984 QModelIndex idx = mLegendModel->node2index( nodeGroup );
985
986 double y = top;
987
988 if ( auto *lPainter = context.painter() )
989 lPainter->setPen( mSettings.fontColor() );
990
991 QFont groupFont = mSettings.style( nodeLegendStyle( nodeGroup ) ).font();
992
993 const double sideMargin = mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Left );
994
995 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.expressionContext() );
996 for ( QStringList::ConstIterator groupPart = lines.constBegin(); groupPart != lines.constEnd(); ++groupPart )
997 {
998 y += mSettings.fontAscentMillimeters( groupFont );
999
1000 if ( QPainter *destPainter = context.painter() )
1001 {
1002 double x = columnContext.left + sideMargin;
1003 if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() != Qt::AlignLeft )
1004 {
1005 const double labelWidth = mSettings.textWidthMillimeters( groupFont, *groupPart );
1006 if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignRight )
1007 x = columnContext.right - labelWidth - sideMargin;
1008 else if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter )
1009 x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
1010 }
1011 mSettings.drawText( destPainter, x, y, *groupPart, groupFont );
1012 }
1013 qreal width = mSettings.textWidthMillimeters( groupFont, *groupPart ) + sideMargin * ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1014 size.rwidth() = std::max( width, size.width() );
1015 if ( groupPart != ( lines.end() - 1 ) )
1016 {
1017 y += mSettings.lineSpacing();
1018 }
1019 }
1020 size.rheight() = y - top + mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Bottom );
1021 return size;
1022}
1023
1025{
1026 QString style = node->customProperty( QStringLiteral( "legend/title-style" ) ).toString();
1027 if ( style == QLatin1String( "hidden" ) )
1029 else if ( style == QLatin1String( "group" ) )
1030 return QgsLegendStyle::Group;
1031 else if ( style == QLatin1String( "subgroup" ) )
1033
1034 // use a default otherwise
1035 if ( QgsLayerTree::isGroup( node ) )
1036 return QgsLegendStyle::Group;
1037 else if ( QgsLayerTree::isLayer( node ) )
1038 {
1039 if ( model->legendNodeEmbeddedInParent( QgsLayerTree::toLayer( node ) ) )
1042 }
1043
1044 return QgsLegendStyle::Undefined; // should not happen, only if corrupted project file
1045}
1046
1048{
1049 return nodeLegendStyle( node, mLegendModel );
1050}
1051
1053{
1054 QString str;
1055 switch ( style )
1056 {
1058 str = QStringLiteral( "hidden" );
1059 break;
1061 str = QStringLiteral( "group" );
1062 break;
1064 str = QStringLiteral( "subgroup" );
1065 break;
1066 default:
1067 break; // nothing
1068 }
1069
1070 if ( !str.isEmpty() )
1071 node->setCustomProperty( QStringLiteral( "legend/title-style" ), str );
1072 else
1073 node->removeCustomProperty( QStringLiteral( "legend/title-style" ) );
1074}
1075
1077{
1078 paintAndDetermineSize( context );
1079}
1080
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Layer tree group node serves as a container for layers and further groups.
Layer tree node points to a map layer.
@ AllowSplittingLegendNodesOverMultipleColumns
Allow splitting node's legend nodes across multiple columns.
@ PreventSplittingLegendNodesOverMultipleColumns
Prevent splitting node's legend nodes across multiple columns.
@ UseDefaultLegendSetting
Inherit default legend column splitting setting.
LegendNodesSplitBehavior legendSplitBehavior() const
Returns the column split behavior for the node.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
QJsonObject exportToJson(const QgsLegendSettings &settings, const QgsRenderContext &context)
Entry point called from QgsLegendRenderer to do the rendering in a JSON object.
virtual bool columnBreak() const
Returns whether a forced column break should occur before the node.
virtual QSizeF userPatchSize() const
Returns the user (overridden) size for the legend node.
virtual ItemMetrics draw(const QgsLegendSettings &settings, ItemContext *ctx)
Entry point called from QgsLegendRenderer to do the rendering.
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QModelIndex node2index(QgsLayerTreeNode *node) const
Returns index for a given node. If the node does not belong to the layer tree, the result is undefine...
QList< QgsLayerTreeModelLegendNode * > layerLegendNodes(QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent=false)
Returns filtered list of active legend nodes attached to a particular layer node (by default it retur...
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
QgsLayerTreeModelLegendNode * legendNodeEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const
Returns legend node that may be embedded in parent (i.e.
const QgsMapSettings * legendFilterMapSettings() const
Returns the current map settings used for the current legend filter (or nullptr if none is enabled)
This class is a base class for nodes in a layer tree.
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:43
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:64
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
QJsonObject exportLegendToJson(const QgsRenderContext &context)
Renders the legend in a json object.
QgsLegendRenderer(QgsLayerTreeModel *legendModel, const QgsLegendSettings &settings)
Constructor for QgsLegendRenderer.
static QgsLegendStyle::Style nodeLegendStyle(QgsLayerTreeNode *node, QgsLayerTreeModel *model)
Returns the style for the given node, within the specified model.
Q_DECL_DEPRECATED void drawLegend(QPainter *painter)
Draws the legend with given painter.
The QgsLegendSettings class stores the appearance and layout settings for legend drawing with QgsLege...
int columnCount() const
Returns the desired minimum number of columns to show in the legend.
QColor layerFontColor() const
Returns layer font color, defaults to fontColor()
void drawText(QPainter *p, double x, double y, const QString &text, const QFont &font) const
Draws Text.
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns the style for a legend component.
double fontDescentMillimeters(const QFont &font) const
Returns the font descent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCA...
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
QColor fontColor() const
Returns the font color used for legend items.
QString title() const
Returns the title for the legend, which will be rendered above all legend items.
double textWidthMillimeters(const QFont &font, const QString &text) const
Returns the font width in millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCALE...
double columnSpace() const
Returns the margin space between adjacent columns (in millimeters).
double fontAscentMillimeters(const QFont &font) const
Returns the font ascent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCAL...
double boxSpace() const
Returns the legend box space (in millimeters), which is the empty margin around the inside of the leg...
double lineSpacing() const
Returns the line spacing to use between lines of legend text.
Q_DECL_DEPRECATED double mmPerMapUnit() const
bool splitLayer() const
Returns true if layer components can be split over multiple columns.
QStringList evaluateItemText(const QString &text, const QgsExpressionContext &context) const
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
QStringList splitStringForWrapping(const QString &stringToSplt) const
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
Qt::AlignmentFlag symbolAlignment() const
Returns the alignment for placement of legend symbols.
bool equalColumnWidth() const
Returns true if all columns should have equal widths.
Q_DECL_DEPRECATED double mapScale() const
Returns the legend map scale.
double margin(Side side)
Returns the margin (in mm) for the specified side of the component.
Qt::Alignment alignment() const
Returns the alignment for the legend component.
QFont font() const
Returns the font used for rendering this legend component.
@ Right
Right side.
@ Left
Left side.
@ Bottom
Bottom side.
@ Top
Top side.
Style
Component of legends which can be styled.
@ Group
Legend group title.
@ Symbol
Symbol icon (excluding label)
@ Undefined
Should not happen, only if corrupted project file.
@ Subgroup
Legend subgroup title.
@ Title
Legend title.
@ Hidden
Special style, item is hidden including margins around.
@ SymbolLabel
Symbol label (excluding icon)
double indent() const
Returns the indent (in mm) of a group or subgroup.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
Scoped object for temporary replacement of a QgsRenderContext destination painter.
Scoped object for temporary scaling of a QgsRenderContext for millimeter based rendering.
Implementation of legend node interface for displaying preview of vector symbols and their labels and...
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
QgsLayerTreeModel * legendModel(const QgsWmsRenderContext &context, QgsLayerTree &tree)
#define str(x)
Definition: qgis.cpp:37
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:3061
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3060
double top
Top y-position of legend item.
Q_DECL_DEPRECATED double labelXOffset
Offset from the left side where label should start.
QgsLegendPatchShape patchShape
The patch shape to render for the node.
double maxSiblingSymbolWidth
Largest symbol width, considering all other sibling legend components associated with the current com...
QSizeF patchSize
Symbol patch size to render for the node.
double columnLeft
Left side of current legend column.
double columnRight
Right side of current legend column.
Q_DECL_DEPRECATED QPointF point
Top-left corner of the legend item.
Q_NOWARN_DEPRECATED_POP QgsRenderContext * context
Render context, if available.