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