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