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