QGIS API Documentation 3.41.0-Master (af5edcb665c)
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;
123 if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
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 // temporarily remove painter from context -- we don't need to actually draw anything yet. But we DO need
210 // to send the full render context so that an expression context is available during the size calculation
211 QgsScopedRenderContextPainterSwap noPainter( context, nullptr );
212
213 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
214
215 const int columnCount = setColumns( componentGroups );
216
217 QMap< int, double > maxColumnWidths;
218 qreal maxEqualColumnWidth = 0;
219 // another iteration -- this one is required to calculate the maximum item width for each
220 // column. Unfortunately, we can't trust the component group widths at this stage, as they are minimal widths
221 // only. When actually rendering a symbol node, the text is aligned according to the WIDEST
222 // symbol in a column. So that means we can't possibly determine the exact size of legend components
223 // until now. BUUUUUUUUUUUUT. Because everything sucks, we can't even start the actual render of items
224 // at the same time we calculate this -- legend items REQUIRE the REAL width of the columns in order to
225 // correctly align right or center-aligned symbols/text. Bah -- A triple iteration it is!
226 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
227 {
228 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
229 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
230 maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
231 }
232
233 if ( columnCount == 1 )
234 {
235 // single column - use the full available width
236 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
237 maxColumnWidths[ 0 ] = maxEqualColumnWidth;
238 }
239
240 //calculate size of title
241 QSizeF titleSize = drawTitle( context, 0 );
242 //add title margin to size of title text
243 titleSize.rwidth() += mSettings.boxSpace() * 2.0;
244 double columnTop = mSettings.boxSpace() + titleSize.height() + mSettings.style( QgsLegendStyle::Title ).margin( QgsLegendStyle::Bottom );
245
246 noPainter.reset();
247
248 bool firstInColumn = true;
249 double columnMaxHeight = 0;
250 qreal columnWidth = 0;
251 int column = -1;
252 ColumnContext columnContext;
253 columnContext.left = mSettings.boxSpace();
254 columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
255 double currentY = columnTop;
256
257 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
258 {
259 if ( group.column > column )
260 {
261 // Switch to next column
262 columnContext.left = group.column > 0 ? ( columnContext.right + mSettings.columnSpace() ) : mSettings.boxSpace();
263 columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
264 columnContext.right = columnContext.left + columnWidth;
265 currentY = columnTop;
266 column++;
267 firstInColumn = true;
268 }
269 if ( !firstInColumn )
270 {
271 currentY += spaceAboveGroup( group );
272 }
273
274 drawGroup( group, context, columnContext, currentY );
275
276 currentY += group.size.height();
277 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
278
279 firstInColumn = false;
280 }
281 const double totalWidth = columnContext.right + mSettings.boxSpace();
282
283 size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
284 size.rwidth() = totalWidth;
285 if ( !mSettings.title().isEmpty() )
286 {
287 size.rwidth() = std::max( titleSize.width(), size.width() );
288 }
289
290 // override the size if it was set by the user
291 if ( mLegendSize.isValid() )
292 {
293 qreal w = std::max( size.width(), mLegendSize.width() );
294 qreal h = std::max( size.height(), mLegendSize.height() );
295 size = QSizeF( w, h );
296 }
297
298 // Now we have set the correct total item width and can draw the title centered
299 if ( !mSettings.title().isEmpty() )
300 {
301 drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
302 }
303
304 return size;
305}
306
307void QgsLegendRenderer::widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, const double legendWidth, double &textBoxWidth, double &textBoxLeft ) const
308{
309 switch ( halignment )
310 {
311 default:
312 textBoxLeft = mSettings.boxSpace();
313 textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
314 break;
315
316 case Qt::AlignHCenter:
317 {
318 // not sure on this logic, I just moved it -- don't blame me for it being totally obscure!
319 const double centerX = legendWidth / 2;
320 textBoxWidth = ( std::min( static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
321 textBoxLeft = centerX - textBoxWidth / 2.;
322 break;
323 }
324 }
325}
326
327QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList( QgsLayerTreeGroup *parentGroup, QgsRenderContext &context, double indent )
328{
329 QList<LegendComponentGroup> componentGroups;
330
331 if ( !parentGroup )
332 return componentGroups;
333
334 const QList<QgsLayerTreeNode *> childNodes = parentGroup->children();
335 for ( QgsLayerTreeNode *node : childNodes )
336 {
337 if ( !mProxyModel->nodeShown( node ) )
338 continue;
339
340 if ( QgsLayerTree::isGroup( node ) )
341 {
342 QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
343 QString style = node->customProperty( QStringLiteral( "legend/title-style" ) ).toString();
344 // Update the required indent for the group/subgroup items, starting from the indent accumulated from parent groups
345 double newIndent = indent;
346 if ( style == QLatin1String( "subgroup" ) )
347 {
348 newIndent += mSettings.style( QgsLegendStyle::Subgroup ).indent( );
349 }
350 else
351 {
352 newIndent += mSettings.style( QgsLegendStyle::Group ).indent( );
353 }
354
355 // Group subitems
356 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
357
358 bool hasSubItems = !subgroups.empty();
359
360 if ( nodeLegendStyle( nodeGroup ) != QgsLegendStyle::Hidden )
361 {
362 LegendComponent component;
363 component.item = node;
364 component.indent = newIndent;
365 component.size = drawGroupTitle( nodeGroup, context );
366
367 if ( !subgroups.isEmpty() )
368 {
369 // Add internal space between this group title and the next component
370 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
371 // Prepend this group title to the first group
372 subgroups[0].components.prepend( component );
373 subgroups[0].size.rheight() += component.size.height();
374 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
375 if ( nodeGroup->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() )
376 subgroups[0].placeColumnBreakBeforeGroup = true;
377 }
378 else
379 {
380 // no subitems, create new group
381 LegendComponentGroup group;
382 group.placeColumnBreakBeforeGroup = nodeGroup->customProperty( QStringLiteral( "legend/column-break" ) ).toInt();
383 group.components.append( component );
384 group.size.rwidth() += component.size.width();
385 group.size.rheight() += component.size.height();
386 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
387 subgroups.append( group );
388 }
389 }
390
391 if ( hasSubItems ) //leave away groups without content
392 {
393 componentGroups.append( subgroups );
394 }
395
396 }
397 else if ( QgsLayerTree::isLayer( node ) )
398 {
399 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
400 QgsLegendStyle::Style layerStyle = nodeLegendStyle( nodeLayer );
401 bool allowColumnSplit = false;
402 switch ( nodeLayer->legendSplitBehavior() )
403 {
405 allowColumnSplit = mSettings.splitLayer();
406 break;
408 allowColumnSplit = true;
409 break;
411 allowColumnSplit = false;
412 break;
413 }
414
415 LegendComponentGroup group;
416 group.placeColumnBreakBeforeGroup = nodeLayer->customProperty( QStringLiteral( "legend/column-break" ) ).toInt();
417
418 if ( layerStyle != QgsLegendStyle::Hidden )
419 {
420 LegendComponent component;
421 component.item = node;
422 component.size = drawLayerTitle( nodeLayer, context );
423 component.indent = indent;
424 group.components.append( component );
425 group.size.rwidth() = component.size.width();
426 group.size.rheight() = component.size.height();
427 }
428
429 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
430
431 // workaround for the issue that "filtering by map" does not remove layer nodes that have no symbols present
432 // on the map. We explicitly skip such layers here. In future ideally that should be handled directly
433 // in the layer tree model
434 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
435 continue;
436
437 QList<LegendComponentGroup> layerGroups;
438 layerGroups.reserve( legendNodes.count() );
439
440 bool groupIsLayerGroup = true;
441 double symbolIndent = indent;
442 switch ( layerStyle )
443 {
446 symbolIndent += mSettings.style( layerStyle ).indent( );
447 break;
448 default:
449 break;
450 }
451 for ( int j = 0; j < legendNodes.count(); j++ )
452 {
453 QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
454
455 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
456
457 const bool forceBreak = legendNode->columnBreak();
458
459 if ( !allowColumnSplit || j == 0 )
460 {
461 if ( forceBreak )
462 {
463 if ( groupIsLayerGroup )
464 layerGroups.prepend( group );
465 else
466 layerGroups.append( group );
467
468 group = LegendComponentGroup();
469 group.placeColumnBreakBeforeGroup = true;
470 groupIsLayerGroup = false;
471 }
472
473 // append to layer group
474 // the width is not correct at this moment, we must align all symbol labels
475 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
476 // Add symbol space only if there is already title or another item above
477 if ( !group.components.isEmpty() )
478 {
479 // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
480 group.size.rheight() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
481 }
482 group.size.rheight() += symbolComponent.size.height();
483 symbolComponent.indent = symbolIndent;
484 group.components.append( symbolComponent );
485 }
486 else
487 {
488 if ( group.size.height() > 0 )
489 {
490 if ( groupIsLayerGroup )
491 layerGroups.prepend( group );
492 else
493 layerGroups.append( group );
494 group = LegendComponentGroup();
495 groupIsLayerGroup = false;
496 }
497 LegendComponentGroup symbolGroup;
498 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
499 symbolComponent.indent = symbolIndent;
500 symbolGroup.components.append( symbolComponent );
501 symbolGroup.size.rwidth() = symbolComponent.size.width();
502 symbolGroup.size.rheight() = symbolComponent.size.height();
503 layerGroups.append( symbolGroup );
504 }
505 }
506 if ( group.size.height() > 0 )
507 {
508 if ( groupIsLayerGroup )
509 layerGroups.prepend( group );
510 else
511 layerGroups.append( group );
512 }
513 componentGroups.append( layerGroups );
514 }
515 }
516
517 return componentGroups;
518}
519
520
521int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
522{
523 // Divide groups to columns
524 double totalHeight = 0;
525 qreal maxGroupHeight = 0;
526 int forcedColumnBreaks = 0;
527 double totalSpaceAboveGroups = 0;
528
529 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
530 {
531 const double topMargin = spaceAboveGroup( group );
532 totalHeight += topMargin;
533 totalSpaceAboveGroups += topMargin;
534
535 const double groupHeight = group.size.height();
536 totalHeight += groupHeight;
537 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
538
539 if ( group.placeColumnBreakBeforeGroup )
540 forcedColumnBreaks++;
541 }
542 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
543 double averageGroupHeight = totalGroupHeight / componentGroups.size();
544
545 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
546 return 0;
547
548 // the target number of columns allowed is dictated by the number of forced column
549 // breaks OR the manually set column count (whichever is greater!)
550 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
551 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
552
553 // We know height of each group and we have to split them into columns
554 // minimizing max column height. It is sort of bin packing problem, NP-hard.
555 // We are using simple heuristic, brute fore appeared to be to slow,
556 // the number of combinations is N = n!/(k!*(n-k)!) where n = groupCount-1
557 // and k = columnsCount-1
558 double maxColumnHeight = 0;
559 int currentColumn = 0;
560 int currentColumnGroupCount = 0; // number of groups in current column
561 double currentColumnHeight = 0;
562 int autoPlacedBreaks = 0;
563
564 // Calculate the expected average space between items
565 double averageSpaceAboveGroups = 0;
566 if ( componentGroups.size() > targetNumberColumns )
567 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
568
569 double totalRemainingGroupHeight = totalGroupHeight;
570 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
571 for ( int i = 0; i < componentGroups.size(); i++ )
572 {
573 const LegendComponentGroup &group = componentGroups.at( i );
574 const double currentGroupHeight = group.size.height();
575 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
576
577 totalRemainingGroupHeight -= currentGroupHeight;
578 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
579
580 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
581 if ( currentColumnGroupCount > 0 )
582 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
583 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
584
585 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
586 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
587 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
588
589 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
590 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
591 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight
592 + totalRemainingSpaceAboveGroups
593 - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
594
595 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded
596 + estimatedRemainingTotalHeightAfterThisGroup;
597
598 // Recalc average height for remaining columns including current
599 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
600
601 // Round up to the next full number of groups to put in one column
602 // This ensures that earlier columns contain more elements than later columns
603 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
604
605 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
606
607 bool canCreateNewColumn = ( currentColumnGroupCount > 0 ) // do not leave empty column
608 && ( currentColumn < targetNumberColumns - 1 ) // must not exceed max number of columns
609 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
610
611 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne // current group height is greater than expected group height
612 && currentColumnGroupCount > 0 // do not leave empty column
613 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight // no sense to make smaller columns than max group height
614 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight; // no sense to make smaller columns than max column already created
615
616 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
617 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
618
619 // also should create a new column if the number of items left < number of columns left
620 // in this case we should spread the remaining items out over the remaining columns
621 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
622
623 if ( canCreateNewColumn && shouldCreateNewColumn )
624 {
625 // New column
626 currentColumn++;
627 if ( !group.placeColumnBreakBeforeGroup )
628 autoPlacedBreaks++;
629 currentColumnGroupCount = 0;
630 currentColumnHeight = group.size.height();
631 }
632 else
633 {
634 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
635 }
636 componentGroups[i].column = currentColumn;
637 currentColumnGroupCount++;
638 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
639 }
640
641 auto refineColumns = [&componentGroups, this]() -> bool
642 {
643 QHash< int, double > columnHeights;
644 QHash< int, int > columnGroupCounts;
645 double currentColumnHeight = 0;
646 int currentColumn = -1;
647 int columnCount = 0;
648 int groupCount = 0;
649 double maxCurrentColumnHeight = 0;
650 for ( int i = 0; i < componentGroups.size(); i++ )
651 {
652 const LegendComponentGroup &group = componentGroups.at( i );
653 if ( group.column != currentColumn )
654 {
655 if ( currentColumn >= 0 )
656 {
657 columnHeights.insert( currentColumn, currentColumnHeight );
658 columnGroupCounts.insert( currentColumn, groupCount );
659 }
660
661 currentColumn = group.column;
662 currentColumnHeight = 0;
663 groupCount = 0;
664 columnCount = std::max( columnCount, currentColumn + 1 );
665 }
666
667 const double spaceAbove = spaceAboveGroup( group );
668 currentColumnHeight += spaceAbove + group.size.height();
669 groupCount++;
670 }
671 columnHeights.insert( currentColumn, currentColumnHeight );
672 columnGroupCounts.insert( currentColumn, groupCount );
673
674 double totalColumnHeights = 0;
675 for ( int i = 0; i < columnCount; ++ i )
676 {
677 totalColumnHeights += columnHeights[i];
678 maxCurrentColumnHeight = std::max( maxCurrentColumnHeight, columnHeights[i] );
679 }
680
681 const double averageColumnHeight = totalColumnHeights / columnCount;
682
683 bool changed = false;
684 int nextCandidateColumnForShift = 1;
685 for ( int i = 0; i < componentGroups.size(); i++ )
686 {
687 LegendComponentGroup &group = componentGroups[ i ];
688 if ( group.column < nextCandidateColumnForShift )
689 continue;
690
691 // try shifting item to previous group
692 const bool canShift = !group.placeColumnBreakBeforeGroup
693 && columnGroupCounts[ group.column ] >= 2;
694
695 if ( canShift
696 && columnHeights[ group.column - 1 ] < averageColumnHeight
697 && ( columnHeights[ group.column - 1 ] + group.size.height() ) * 0.9 < maxCurrentColumnHeight
698 )
699 {
700 group.column -= 1;
701 columnHeights[ group.column ] += group.size.height() + spaceAboveGroup( group );
702 columnGroupCounts[ group.column ]++;
703 columnHeights[ group.column + 1 ] -= group.size.height();
704 columnGroupCounts[ group.column + 1]--;
705 changed = true;
706 }
707 else
708 {
709 nextCandidateColumnForShift = group.column + 1;
710 }
711 }
712 return changed;
713 };
714
715 bool wasRefined = true;
716 int iterations = 0;
717 while ( wasRefined && iterations < 2 )
718 {
719 wasRefined = refineColumns();
720 iterations++;
721 }
722
723 // Align labels of symbols for each layer/column to the same labelXOffset
724 QMap<QString, qreal> maxSymbolWidth;
725 for ( int i = 0; i < componentGroups.size(); i++ )
726 {
727 LegendComponentGroup &group = componentGroups[i];
728 for ( int j = 0; j < group.components.size(); j++ )
729 {
730 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
731 {
732 QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
733 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
734 }
735 }
736 }
737 for ( int i = 0; i < componentGroups.size(); i++ )
738 {
739 LegendComponentGroup &group = componentGroups[i];
740 for ( int j = 0; j < group.components.size(); j++ )
741 {
742 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
743 {
744 QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
745 double space = mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right ) +
747 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
748 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
749 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
750 }
751 }
752 }
753 return targetNumberColumns;
754}
755
756QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext &context, double top, Qt::AlignmentFlag halignment, double legendWidth ) const
757{
758 QSizeF size( 0, 0 );
759 if ( mSettings.title().isEmpty() )
760 {
761 return size;
762 }
763
764 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
765
766 //calculate width and left pos of rectangle to draw text into
767 double textBoxWidth;
768 double textBoxLeft;
769 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
770
771 const QgsTextFormat titleFormat = mSettings.style( QgsLegendStyle::Title ).textFormat();
772 const double dotsPerMM = context.scaleFactor();
773
774 double overallTextHeight = 0;
775 double overallTextWidth = 0;
776
777 {
778 QgsScopedRenderContextScaleToPixels contextToPixels( context );
779 overallTextHeight = QgsTextRenderer::textHeight( context, titleFormat, lines, Qgis::TextLayoutMode::Rectangle );
780 overallTextWidth = QgsTextRenderer::textWidth( context, titleFormat, lines );
781 }
782
783 size.rheight() = overallTextHeight / dotsPerMM;
784 size.rwidth() = overallTextWidth / dotsPerMM;
785
786 if ( context.painter() )
787 {
788 QgsScopedRenderContextScaleToPixels contextToPixels( context );
789
790 const QRectF r( textBoxLeft * dotsPerMM, top * dotsPerMM, textBoxWidth * dotsPerMM, overallTextHeight );
791
792 Qgis::TextHorizontalAlignment halign = halignment == Qt::AlignLeft ? Qgis::TextHorizontalAlignment::Left :
794
795 QgsTextRenderer::drawText( r, 0, halign, lines, context, titleFormat );
796 }
797
798 return size;
799}
800
801
802double QgsLegendRenderer::spaceAboveGroup( const LegendComponentGroup &group )
803{
804 if ( group.components.isEmpty() ) return 0;
805
806 LegendComponent component = group.components.first();
807
808 if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
809 {
810 return mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Top );
811 }
812 else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
813 {
814 return mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Top );
815 }
816 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
817 {
818 // TODO: use Symbol or SymbolLabel Top margin
820 }
821
822 return 0;
823}
824
825QSizeF QgsLegendRenderer::drawGroup( const LegendComponentGroup &group, QgsRenderContext &context, ColumnContext columnContext, double top )
826{
827 bool first = true;
828 QSizeF size = QSizeF( group.size );
829 double currentY = top;
830 for ( const LegendComponent &component : std::as_const( group.components ) )
831 {
832 if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
833 {
834 QgsLegendStyle::Style s = nodeLegendStyle( groupItem );
835 if ( s != QgsLegendStyle::Hidden )
836 {
837 if ( !first )
838 {
839 currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
840 }
841 QSizeF groupSize;
842 ColumnContext columnContextForItem = columnContext;
843 double indentWidth = component.indent;
844 if ( s == QgsLegendStyle::Subgroup )
845 {
846 // Remove indent - the subgroup items should be indented, not the subgroup title
847 indentWidth -= mSettings.style( QgsLegendStyle::Subgroup ).indent( );
848 }
849 else
850 {
851 // Remove indent - the group items should be indented, not the group title
852 indentWidth -= mSettings.style( QgsLegendStyle::Group ).indent( );
853 }
854 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignLeft )
855 {
856 columnContextForItem.left += indentWidth;
857 }
858 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignRight )
859 {
860 columnContextForItem.right -= indentWidth;
861 }
862 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
863 size.rwidth() = std::max( groupSize.width(), size.width() );
864 }
865 }
866 else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
867 {
868 QgsLegendStyle::Style s = nodeLegendStyle( layerItem );
869 if ( s != QgsLegendStyle::Hidden )
870 {
871 if ( !first )
872 {
873 currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
874 }
875 QSizeF subGroupSize;
876
877 ColumnContext columnContextForItem = columnContext;
878 double indentWidth = component.indent;
879 columnContextForItem.left += indentWidth;
880 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
881 size.rwidth() = std::max( subGroupSize.width(), size.width() );
882 }
883 }
884 else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
885 {
886 if ( !first )
887 {
888 currentY += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
889 }
890
891 ColumnContext columnContextForItem = columnContext;
892 double indentWidth = 0;
893 indentWidth = component.indent;
894 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignLeft )
895 {
896 columnContextForItem.left += indentWidth;
897 }
898 if ( mSettings.style( QgsLegendStyle::SymbolLabel ).alignment() == Qt::AlignRight )
899 {
900 columnContextForItem.right -= indentWidth;
901 }
902
903 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
904 // expand width, it may be wider because of label offsets
905 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
906 }
907 currentY += component.size.height();
908 first = false;
909 }
910 return size;
911}
912
913QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext &context, ColumnContext columnContext, double top, double maxSiblingSymbolWidth )
914{
916 ctx.context = &context;
917
918 // add a layer expression context scope
919 QgsExpressionContextScope *layerScope = nullptr;
920 if ( symbolItem->layerNode()->layer() )
921 {
922 layerScope = QgsExpressionContextUtils::layerScope( symbolItem->layerNode()->layer() );
923 context.expressionContext().appendScope( layerScope );
924 }
925
926 ctx.painter = context.painter();
928 ctx.point = QPointF( columnContext.left, top );
929 ctx.labelXOffset = maxSiblingSymbolWidth;
931
932 ctx.top = top;
933
934 ctx.columnLeft = columnContext.left;
935 ctx.columnRight = columnContext.right;
936
937 switch ( mSettings.symbolAlignment() )
938 {
939 case Qt::AlignLeft:
940 default:
942 break;
943
944 case Qt::AlignRight:
946 break;
947 }
948
949 ctx.maxSiblingSymbolWidth = maxSiblingSymbolWidth;
950
951 QgsExpressionContextScope *symbolScope = nullptr;
952 if ( const QgsSymbolLegendNode *symbolNode = dynamic_cast< const QgsSymbolLegendNode * >( symbolItem ) )
953 {
954 symbolScope = symbolNode->createSymbolScope();
955 context.expressionContext().appendScope( symbolScope );
956 ctx.patchShape = symbolNode->patchShape();
957 }
958
959 ctx.patchSize = symbolItem->userPatchSize();
960
961 QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->draw( mSettings, &ctx );
962
963 if ( symbolScope )
964 delete context.expressionContext().popScope();
965
966 if ( layerScope )
967 delete context.expressionContext().popScope();
968
969 LegendComponent component;
970 component.item = symbolItem;
971 component.symbolSize = im.symbolSize;
972 component.labelSize = im.labelSize;
973 //QgsDebugMsgLevel( QStringLiteral( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ), 2);
974 // NOTE -- we hard code left/right margins below, because those are the only ones exposed for use currently.
975 // ideally we could (should?) expose all these margins as settings, and then adapt the below to respect the current symbol/text alignment
976 // and consider the correct margin sides...
977 double width = std::max( static_cast< double >( im.symbolSize.width() ), maxSiblingSymbolWidth )
981 + im.labelSize.width();
982
983 double height = std::max( im.symbolSize.height(), im.labelSize.height() );
984 component.size = QSizeF( width, height );
985 return component;
986}
987
988QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext &context, ColumnContext columnContext, double top )
989{
990 QSizeF size( 0, 0 );
991 QModelIndex idx = mLegendModel->node2index( nodeLayer );
992 QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
993 //Let the user omit the layer title item by having an empty layer title string
994 if ( titleString.isEmpty() )
995 return size;
996
997 const QgsTextFormat layerFormat = mSettings.style( nodeLegendStyle( nodeLayer ) ).textFormat();
998
999 QgsExpressionContextScope *layerScope = nullptr;
1000 if ( nodeLayer->layer() )
1001 {
1002 layerScope = QgsExpressionContextUtils::layerScope( nodeLayer->layer() );
1003 context.expressionContext().appendScope( layerScope );
1004 }
1005
1006 const QStringList lines = mSettings.evaluateItemText( titleString, context.expressionContext() );
1007
1008 const double dotsPerMM = context.scaleFactor();
1009
1010 double overallTextHeight = 0;
1011 double overallTextWidth = 0;
1012 {
1013 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1014 overallTextHeight = QgsTextRenderer::textHeight( context, layerFormat, lines, Qgis::TextLayoutMode::RectangleAscentBased );
1015 overallTextWidth = QgsTextRenderer::textWidth( context, layerFormat, lines );
1016 }
1017 const double sideMargin = mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Left );
1018
1019 size.rheight() = overallTextHeight / dotsPerMM;
1020 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1021 ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1022
1023 if ( context.painter() )
1024 {
1025 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1026 Qgis::TextHorizontalAlignment halign = mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignLeft ? Qgis::TextHorizontalAlignment::Left :
1028
1029 const QRectF r( ( columnContext.left + ( halign == Qgis::TextHorizontalAlignment::Left ? sideMargin : 0 ) ) * dotsPerMM, top * dotsPerMM,
1030 ( ( columnContext.right - columnContext.left ) - ( halign == Qgis::TextHorizontalAlignment::Right ? sideMargin : 0 ) ) * dotsPerMM, overallTextHeight );
1031 QgsTextRenderer::drawText( r, 0, halign, lines, context, layerFormat );
1032 }
1033
1034 size.rheight() += mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Side::Bottom );
1035
1036 if ( layerScope )
1037 delete context.expressionContext().popScope();
1038
1039 return size;
1040}
1041
1042QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext &context, ColumnContext columnContext, double top )
1043{
1044 QSizeF size( 0, 0 );
1045 QModelIndex idx = mLegendModel->node2index( nodeGroup );
1046
1047 const QgsTextFormat groupFormat = mSettings.style( nodeLegendStyle( nodeGroup ) ).textFormat();
1048
1049 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.expressionContext() );
1050
1051 double overallTextHeight = 0;
1052 double overallTextWidth = 0;
1053
1054 {
1055 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1056 overallTextHeight = QgsTextRenderer::textHeight( context, groupFormat, lines, Qgis::TextLayoutMode::RectangleAscentBased );
1057 overallTextWidth = QgsTextRenderer::textWidth( context, groupFormat, lines );
1058 }
1059
1060 const double sideMargin = mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Left );
1061 const double dotsPerMM = context.scaleFactor();
1062
1063 size.rheight() = overallTextHeight / dotsPerMM;
1064 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1065 ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1066
1067 if ( context.painter() )
1068 {
1069 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1070
1071 Qgis::TextHorizontalAlignment halign = mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignLeft ? Qgis::TextHorizontalAlignment::Left :
1073
1074 const QRectF r( dotsPerMM * ( columnContext.left + ( halign == Qgis::TextHorizontalAlignment::Left ? sideMargin : 0 ) ), top * dotsPerMM,
1075 dotsPerMM * ( ( columnContext.right - columnContext.left ) - ( halign == Qgis::TextHorizontalAlignment::Right ? sideMargin : 0 ) ), overallTextHeight );
1076 QgsTextRenderer::drawText( r, 0, halign, lines, context, groupFormat );
1077 }
1078
1079 size.rheight() += mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Bottom );
1080 return size;
1081}
1082
1084{
1085 QString style = node->customProperty( QStringLiteral( "legend/title-style" ) ).toString();
1086 if ( style == QLatin1String( "hidden" ) )
1088 else if ( style == QLatin1String( "group" ) )
1089 return QgsLegendStyle::Group;
1090 else if ( style == QLatin1String( "subgroup" ) )
1092
1093 // use a default otherwise
1094 if ( QgsLayerTree::isGroup( node ) )
1095 return QgsLegendStyle::Group;
1096 else if ( QgsLayerTree::isLayer( node ) )
1097 {
1098 if ( model->legendNodeEmbeddedInParent( QgsLayerTree::toLayer( node ) ) )
1101 }
1102
1103 return QgsLegendStyle::Undefined; // should not happen, only if corrupted project file
1104}
1105
1107{
1108 return nodeLegendStyle( node, mLegendModel );
1109}
1110
1112{
1113 return mProxyModel.get();
1114}
1115
1117{
1118 QString str;
1119 switch ( style )
1120 {
1122 str = QStringLiteral( "hidden" );
1123 break;
1125 str = QStringLiteral( "group" );
1126 break;
1128 str = QStringLiteral( "subgroup" );
1129 break;
1130 default:
1131 break; // nothing
1132 }
1133
1134 if ( !str.isEmpty() )
1135 node->setCustomProperty( QStringLiteral( "legend/title-style" ), str );
1136 else
1137 node->removeCustomProperty( QStringLiteral( "legend/title-style" ) );
1138}
1139
1141{
1142 paintAndDetermineSize( context );
1143}
1144
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.
@ 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:2803
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.
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
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.
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...
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.
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.
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:6643
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6642
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.