35using namespace Qt::StringLiterals;
38 : mLegendModel( legendModel )
40 , mSettings( settings )
42 mProxyModel->setLayerTreeModel( mLegendModel );
46 : mLegendModel( other.mLegendModel )
47 , mProxyModel( std::move( other.mProxyModel ) )
48 , mSettings( std::move( other.mSettings ) )
49 , mLegendSize( other.mLegendSize )
51 mProxyModel->setLayerTreeModel( mLegendModel );
56 if ( mProxyModel.get() == model )
59 mProxyModel.reset( model );
60 mProxyModel->setLayerTreeModel( mLegendModel );
67 std::unique_ptr< QgsRenderContext > tmpContext;
74 tmpContext->setRendererScale( mSettings.mapScale() );
75 tmpContext->setMapToPixel(
QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * tmpContext->scaleFactor() ) ) );
77 renderContext = tmpContext.get();
82 return paintAndDetermineSize( *renderContext );
91 context.setRendererScale( mSettings.mapScale() );
92 context.setMapToPixel(
QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * context.scaleFactor() ) ) );
95 paintAndDetermineSize( context );
107 json[u
"title"_s] = mSettings.title();
115 const QList<QgsLayerTreeNode *> childNodes = nodeGroup->
children();
118 if ( !mProxyModel->nodeShown( node ) )
124 const QModelIndex idx = mLegendModel->
node2index( nodeGroup );
125 const QString text = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
128 group[u
"type"_s] = u
"group"_s;
129 group[u
"title"_s] = text;
130 nodes.append( group );
139 const QModelIndex idx = mLegendModel->node2index( nodeLayer );
140 text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
143 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
145 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
148 if ( legendNodes.count() == 1 )
150 QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
151 group[u
"type"_s] = u
"layer"_s;
154 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->
layer() ) )
156 if ( vLayer->renderer() )
159 if ( !ruleKey.isEmpty() )
162 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
165 group[u
"rule"_s] = ruleExp;
171 nodes.append( group );
173 else if ( legendNodes.count() > 1 )
176 group[u
"type"_s] = u
"layer"_s;
177 group[u
"title"_s] = text;
180 for (
int j = 0; j < legendNodes.count(); j++ )
182 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
186 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->
layer() ) )
188 if ( vLayer->renderer() )
191 if ( !ruleKey.isEmpty() )
194 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
197 symbol[u
"rule"_s] = ruleExp;
203 symbols.append( symbol );
205 group[u
"symbols"_s] = symbols;
207 nodes.append( group );
212 json[u
"nodes"_s] = nodes;
219 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
223 mSettings.updateDataDefinedProperties( context );
227 QgsScopedRenderContextPainterSwap noPainter( context,
nullptr );
229 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
231 const int columnCount = setColumns( componentGroups );
233 QMap< int, double > maxColumnWidths;
234 qreal maxEqualColumnWidth = 0;
242 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
244 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
245 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
246 maxColumnWidths[group.column] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
249 if ( columnCount == 1 )
252 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
253 maxColumnWidths[0] = maxEqualColumnWidth;
257 QSizeF titleSize = drawTitle( context, 0 );
259 titleSize.rwidth() += mSettings.boxSpace() * 2.0;
264 bool firstInColumn =
true;
265 double columnMaxHeight = 0;
266 qreal columnWidth = 0;
268 ColumnContext columnContext;
269 columnContext.left = mSettings.boxSpace();
270 columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
271 double currentY = columnTop;
273 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
275 if ( group.column > column )
278 columnContext.left = group.column > 0 ? ( columnContext.right + mSettings.columnSpace() ) : mSettings.boxSpace();
279 columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
280 columnContext.right = columnContext.left + columnWidth;
281 currentY = columnTop;
283 firstInColumn =
true;
285 if ( !firstInColumn )
287 currentY += spaceAboveGroup( group );
290 drawGroup( group, context, columnContext, currentY );
292 currentY += group.size.height();
293 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
295 firstInColumn =
false;
297 const double totalWidth = columnContext.right + mSettings.boxSpace();
299 size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
300 size.rwidth() = totalWidth;
301 if ( !mSettings.title().isEmpty() )
303 size.rwidth() = std::max( titleSize.width(), size.width() );
307 if ( mLegendSize.isValid() )
309 qreal w = std::max( size.width(), mLegendSize.width() );
310 qreal h = std::max( size.height(), mLegendSize.height() );
311 size = QSizeF( w, h );
315 if ( !mSettings.title().isEmpty() )
317 drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
323void QgsLegendRenderer::widthAndOffsetForTitleText(
const Qt::AlignmentFlag halignment,
const double legendWidth,
double &textBoxWidth,
double &textBoxLeft )
const
325 switch ( halignment )
328 textBoxLeft = mSettings.boxSpace();
329 textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
332 case Qt::AlignHCenter:
335 const double centerX = legendWidth / 2;
336 textBoxWidth = ( std::min(
static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
337 textBoxLeft = centerX - textBoxWidth / 2.;
343QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList(
QgsLayerTreeGroup *parentGroup,
QgsRenderContext &context,
double indent )
345 QList<LegendComponentGroup> componentGroups;
348 return componentGroups;
350 const QList<QgsLayerTreeNode *> childNodes = parentGroup->
children();
351 for ( QgsLayerTreeNode *node : childNodes )
353 if ( !mProxyModel->nodeShown( node ) )
359 QString style = node->customProperty( u
"legend/title-style"_s ).toString();
361 double newIndent = indent;
362 if ( style ==
"subgroup"_L1 )
372 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
374 bool hasSubItems = !subgroups.empty();
378 LegendComponent component;
379 component.item = node;
380 component.indent = newIndent;
381 component.size = drawGroupTitle( nodeGroup, context );
383 if ( !subgroups.isEmpty() )
386 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
388 subgroups[0].components.prepend( component );
389 subgroups[0].size.rheight() += component.size.height();
390 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
391 if ( nodeGroup->
customProperty( u
"legend/column-break"_s ).toInt() )
392 subgroups[0].placeColumnBreakBeforeGroup =
true;
397 LegendComponentGroup group;
398 group.placeColumnBreakBeforeGroup = nodeGroup->
customProperty( u
"legend/column-break"_s ).toInt();
399 group.components.append( component );
400 group.size.rwidth() += component.size.width();
401 group.size.rheight() += component.size.height();
402 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
403 subgroups.append( group );
409 componentGroups.append( subgroups );
416 bool allowColumnSplit =
false;
420 allowColumnSplit = mSettings.splitLayer();
423 allowColumnSplit =
true;
426 allowColumnSplit =
false;
430 LegendComponentGroup group;
431 group.placeColumnBreakBeforeGroup = nodeLayer->
customProperty( u
"legend/column-break"_s ).toInt();
435 LegendComponent component;
436 component.item = node;
437 component.size = drawLayerTitle( nodeLayer, context );
438 component.indent = indent;
439 group.components.append( component );
440 group.size.rwidth() = component.size.width();
441 group.size.rheight() = component.size.height();
444 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
449 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
452 QList<LegendComponentGroup> layerGroups;
453 layerGroups.reserve( legendNodes.count() );
455 bool groupIsLayerGroup =
true;
456 double symbolIndent = indent;
457 switch ( layerStyle )
461 symbolIndent += mSettings.style( layerStyle ).indent();
466 for (
int j = 0; j < legendNodes.count(); j++ )
468 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
470 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
474 if ( !allowColumnSplit || j == 0 )
478 if ( groupIsLayerGroup )
479 layerGroups.prepend( group );
481 layerGroups.append( group );
483 group = LegendComponentGroup();
484 group.placeColumnBreakBeforeGroup =
true;
485 groupIsLayerGroup =
false;
490 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
492 if ( !group.components.isEmpty() )
497 group.size.rheight() += symbolComponent.size.height();
498 symbolComponent.indent = symbolIndent;
499 group.components.append( symbolComponent );
503 if ( group.size.height() > 0 )
505 if ( groupIsLayerGroup )
506 layerGroups.prepend( group );
508 layerGroups.append( group );
509 group = LegendComponentGroup();
510 groupIsLayerGroup =
false;
512 LegendComponentGroup symbolGroup;
513 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
514 symbolComponent.indent = symbolIndent;
515 symbolGroup.components.append( symbolComponent );
516 symbolGroup.size.rwidth() = symbolComponent.size.width();
517 symbolGroup.size.rheight() = symbolComponent.size.height();
518 layerGroups.append( symbolGroup );
521 if ( group.size.height() > 0 )
523 if ( groupIsLayerGroup )
524 layerGroups.prepend( group );
526 layerGroups.append( group );
528 componentGroups.append( layerGroups );
532 return componentGroups;
536int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
539 double totalHeight = 0;
540 qreal maxGroupHeight = 0;
541 int forcedColumnBreaks = 0;
542 double totalSpaceAboveGroups = 0;
544 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
546 const double topMargin = spaceAboveGroup( group );
547 totalHeight += topMargin;
548 totalSpaceAboveGroups += topMargin;
550 const double groupHeight = group.size.height();
551 totalHeight += groupHeight;
552 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
554 if ( group.placeColumnBreakBeforeGroup )
555 forcedColumnBreaks++;
557 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
558 double averageGroupHeight = totalGroupHeight / componentGroups.size();
560 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
565 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
566 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
573 double maxColumnHeight = 0;
574 int currentColumn = 0;
575 int currentColumnGroupCount = 0;
576 double currentColumnHeight = 0;
577 int autoPlacedBreaks = 0;
580 double averageSpaceAboveGroups = 0;
581 if ( componentGroups.size() > targetNumberColumns )
582 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
584 double totalRemainingGroupHeight = totalGroupHeight;
585 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
586 for (
int i = 0; i < componentGroups.size(); i++ )
588 const LegendComponentGroup &group = componentGroups.at( i );
589 const double currentGroupHeight = group.size.height();
590 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
592 totalRemainingGroupHeight -= currentGroupHeight;
593 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
595 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
596 if ( currentColumnGroupCount > 0 )
597 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
598 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
600 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
601 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
602 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
604 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
605 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
606 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight + totalRemainingSpaceAboveGroups - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
608 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded + estimatedRemainingTotalHeightAfterThisGroup;
611 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
615 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
617 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
619 bool canCreateNewColumn = ( currentColumnGroupCount > 0 )
620 && ( currentColumn < targetNumberColumns - 1 )
621 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
623 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne
624 && currentColumnGroupCount > 0
625 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight
626 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight;
628 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
629 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
633 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
635 if ( canCreateNewColumn && shouldCreateNewColumn )
639 if ( !group.placeColumnBreakBeforeGroup )
641 currentColumnGroupCount = 0;
642 currentColumnHeight = group.size.height();
646 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
648 componentGroups[i].column = currentColumn;
649 currentColumnGroupCount++;
650 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
653 auto refineColumns = [&componentGroups,
this]() ->
bool {
654 QHash< int, double > columnHeights;
655 QHash< int, int > columnGroupCounts;
656 double currentColumnHeight = 0;
657 int currentColumn = -1;
660 double maxCurrentColumnHeight = 0;
661 for (
int i = 0; i < componentGroups.size(); i++ )
663 const LegendComponentGroup &group = componentGroups.at( i );
664 if ( group.column != currentColumn )
666 if ( currentColumn >= 0 )
668 columnHeights.insert( currentColumn, currentColumnHeight );
669 columnGroupCounts.insert( currentColumn, groupCount );
672 currentColumn = group.column;
673 currentColumnHeight = 0;
675 columnCount = std::max( columnCount, currentColumn + 1 );
678 const double spaceAbove = spaceAboveGroup( group );
679 currentColumnHeight += spaceAbove + group.size.height();
682 columnHeights.insert( currentColumn, currentColumnHeight );
683 columnGroupCounts.insert( currentColumn, groupCount );
685 double totalColumnHeights = 0;
686 for (
int i = 0; i < columnCount; ++i )
688 totalColumnHeights += columnHeights[i];
689 maxCurrentColumnHeight = std::max( maxCurrentColumnHeight, columnHeights[i] );
692 const double averageColumnHeight = totalColumnHeights / columnCount;
694 bool changed =
false;
695 int nextCandidateColumnForShift = 1;
696 for (
int i = 0; i < componentGroups.size(); i++ )
698 LegendComponentGroup &group = componentGroups[i];
699 if ( group.column < nextCandidateColumnForShift )
703 const bool canShift = !group.placeColumnBreakBeforeGroup && columnGroupCounts[group.column] >= 2;
705 if ( canShift && columnHeights[group.column - 1] < averageColumnHeight && ( columnHeights[group.column - 1] + group.size.height() ) * 0.9 < maxCurrentColumnHeight )
708 columnHeights[group.column] += group.size.height() + spaceAboveGroup( group );
709 columnGroupCounts[group.column]++;
710 columnHeights[group.column + 1] -= group.size.height();
711 columnGroupCounts[group.column + 1]--;
716 nextCandidateColumnForShift = group.column + 1;
722 bool wasRefined =
true;
724 while ( wasRefined && iterations < 2 )
726 wasRefined = refineColumns();
731 QMap<QString, qreal> maxSymbolWidth;
732 for (
int i = 0; i < componentGroups.size(); i++ )
734 LegendComponentGroup &group = componentGroups[i];
735 for (
int j = 0; j < group.components.size(); j++ )
737 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
739 QString key = u
"%1-%2"_s.arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
740 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
744 for (
int i = 0; i < componentGroups.size(); i++ )
746 LegendComponentGroup &group = componentGroups[i];
747 for (
int j = 0; j < group.components.size(); j++ )
749 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
751 QString key = u
"%1-%2"_s.arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
753 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
754 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
755 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
759 return targetNumberColumns;
762QSizeF QgsLegendRenderer::drawTitle(
QgsRenderContext &context,
double top, Qt::AlignmentFlag halignment,
double legendWidth )
const
765 if ( mSettings.title().isEmpty() )
773 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
776 QgsTextDocumentRenderContext documentContext;
777 QgsScopedRenderContextScaleToPixels scaleToPx( context );
778 if ( mSettings.autoWrapLinesAfter() > 0 )
787 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
794 size.rheight() = overallTextHeight / dotsPerMM;
795 size.rwidth() = overallTextWidth / dotsPerMM;
799 const QRectF r( textBoxLeft * dotsPerMM, top * dotsPerMM, textBoxWidth * dotsPerMM, overallTextHeight );
812double QgsLegendRenderer::spaceAboveGroup(
const LegendComponentGroup &group )
814 if ( group.components.isEmpty() )
817 LegendComponent component = group.components.first();
819 if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
823 else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
827 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
836QSizeF QgsLegendRenderer::drawGroup(
const LegendComponentGroup &group,
QgsRenderContext &context, ColumnContext columnContext,
double top )
839 QSizeF size = QSizeF( group.size );
840 double currentY = top;
841 for (
const LegendComponent &component : std::as_const( group.components ) )
843 if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
853 ColumnContext columnContextForItem = columnContext;
854 double indentWidth = component.indent;
867 columnContextForItem.left += indentWidth;
871 columnContextForItem.right -= indentWidth;
873 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
874 size.rwidth() = std::max( groupSize.width(), size.width() );
877 else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
888 ColumnContext columnContextForItem = columnContext;
889 double indentWidth = component.indent;
890 columnContextForItem.left += indentWidth;
891 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
892 size.rwidth() = std::max( subGroupSize.width(), size.width() );
895 else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
902 ColumnContext columnContextForItem = columnContext;
903 double indentWidth = 0;
904 indentWidth = component.indent;
907 columnContextForItem.left += indentWidth;
911 columnContextForItem.right -= indentWidth;
914 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
916 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
918 currentY += component.size.height();
926 QgsLayerTreeModelLegendNode::ItemContext ctx;
930 QgsExpressionContextScope *layerScope =
nullptr;
939 ctx.
point = QPointF( columnContext.left, top );
948 switch ( mSettings.symbolAlignment() )
962 QgsExpressionContextScope *symbolScope =
nullptr;
963 if (
const QgsSymbolLegendNode *symbolNode =
dynamic_cast< const QgsSymbolLegendNode *
>( symbolItem ) )
965 symbolScope = symbolNode->createSymbolScope();
972 QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->
draw( mSettings, ctx );
980 LegendComponent component;
981 component.item = symbolItem;
988 double width = std::max(
static_cast< double >( im.
symbolSize.width() ), maxSiblingSymbolWidth )
995 component.size = QSizeF( width, height );
1001 QSizeF size( 0, 0 );
1002 QModelIndex idx = mLegendModel->node2index( nodeLayer );
1003 QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
1005 if ( titleString.isEmpty() )
1010 const QgsTextFormat layerFormat = mSettings.style(
nodeLegendStyle( nodeLayer ) ).textFormat();
1012 QgsExpressionContextScope *layerScope =
nullptr;
1013 if ( nodeLayer->
layer() )
1019 const QStringList lines = mSettings.evaluateItemText( titleString, context.
expressionContext() );
1022 QgsTextDocumentRenderContext documentContext;
1023 QgsScopedRenderContextScaleToPixels scaleToPx( context );
1024 if ( mSettings.autoWrapLinesAfter() > 0 )
1038 size.rheight() = overallTextHeight / dotsPerMM;
1039 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin * ( mSettings.style(
nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1050 overallTextHeight );
1064 QSizeF size( 0, 0 );
1065 QModelIndex idx = mLegendModel->node2index( nodeGroup );
1069 const QgsTextFormat groupFormat = mSettings.style(
nodeLegendStyle( nodeGroup ) ).textFormat();
1071 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.
expressionContext() );
1074 QgsTextDocumentRenderContext documentContext;
1075 QgsScopedRenderContextScaleToPixels scaleToPx( context );
1076 if ( mSettings.autoWrapLinesAfter() > 0 )
1090 size.rheight() = overallTextHeight / dotsPerMM;
1091 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin * ( mSettings.style(
nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1103 overallTextHeight );
1113 QString style = node->
customProperty( u
"legend/title-style"_s ).toString();
1114 if ( style ==
"hidden"_L1 )
1116 else if ( style ==
"group"_L1 )
1118 else if ( style ==
"subgroup"_L1 )
1141 return mProxyModel.get();
1156 str = u
"subgroup"_s;
1162 if ( !str.isEmpty() )
1170 paintAndDetermineSize( context );
LegendComponent
Component of legends which can be styled.
@ Symbol
Symbol icon (excluding label).
@ Group
Legend group title.
@ Hidden
Special style, item is hidden including margins around.
@ Subgroup
Legend subgroup title.
@ SymbolLabel
Symbol label (excluding icon).
@ Undefined
Should not happen, only if corrupted project file.
@ RectangleAscentBased
Similar to Rectangle mode, but uses ascents only when calculating font and line heights.
@ Rectangle
Text within rectangle layout mode.
@ ShowRuleDetails
If set, the rule expression of a rule based renderer legend item will be added to the JSON.
@ Horizontal
Horizontally oriented text.
@ Millimeters
Millimeters.
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
TextHorizontalAlignment
Text horizontal alignment.
@ WrapLines
Automatically wrap long lines of text.
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.
A sort filter proxy model to easily reproduce the legend/layer tree in a tree view.
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.
An abstract interface for legend items returned from QgsMapLayerLegend implementation.
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 ItemMetrics draw(const QgsLegendSettings &settings, ItemContext &ctx)
Entry point called from QgsLegendRenderer to do the rendering.
virtual QSizeF userPatchSize() const
Returns the user (overridden) size for the legend node.
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
A model representing the layer tree, including layers and groups of layers.
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...
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QgsLayerTreeModelLegendNode * legendNodeEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const
Returns legend node that may be embedded in parent (i.e.
Base class for nodes in a layer tree.
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
QgsLayerTreeFilterProxyModel * proxyModel()
Returns the filter proxy model used for filtering the legend model content during rendering.
QJsonObject exportLegendToJson(const QgsRenderContext &context)
Renders the legend in a json object.
QgsLegendRenderer(QgsLayerTreeModel *legendModel, const QgsLegendSettings &settings)
Constructor for QgsLegendRenderer.
Q_DECL_DEPRECATED void drawLegend(QPainter *painter)
Draws the legend with given painter.
static void setNodeLegendStyle(QgsLayerTreeNode *node, Qgis::LegendComponent style)
Sets the style of a node.
static Qgis::LegendComponent nodeLegendStyle(QgsLayerTreeNode *node, QgsLayerTreeModel *model)
Returns the style for the given node, within the specified model.
void setProxyModel(QgsLayerTreeFilterProxyModel *model)
Sets the filter proxy model to use for filtering the legend model content during rendering.
Stores the appearance and layout settings for legend drawing with QgsLegendRenderer.
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.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
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.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0, const QgsTextDocumentRenderContext &documentContext=QgsTextDocumentRenderContext())
Returns precalculated text metrics for a text document, when rendered using the given base format and...
const QgsTextDocument & document() const
Returns the document associated with the calculated metrics.
void setFlags(Qgis::TextRendererFlags flags)
Sets associated text renderer flags.
void setMaximumWidth(double width)
Sets the maximum width (in painter units) for rendered text.
static QgsTextDocument fromTextAndFormat(const QStringList &lines, const QgsTextFormat &format)
Constructor for QgsTextDocument consisting of a set of lines, respecting settings from a text format.
static void drawDocument(const QRectF &rect, const QgsTextFormat &format, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, Qgis::TextHorizontalAlignment horizontalAlignment=Qgis::TextHorizontalAlignment::Left, Qgis::TextVerticalAlignment verticalAlignment=Qgis::TextVerticalAlignment::Top, double rotation=0, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws a text document within a rectangle using the specified settings.
static double calculateScaleFactorForFormat(const QgsRenderContext &context, const QgsTextFormat &format)
Returns the scale factor used for upscaling font sizes and downscaling destination painter devices.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
QPainter * painter
Painter.
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.