33using namespace Qt::StringLiterals;
36 : mLegendModel( legendModel )
38 , mSettings( settings )
40 mProxyModel->setLayerTreeModel( mLegendModel );
44 : mLegendModel( other.mLegendModel )
45 , mProxyModel( std::move( other.mProxyModel ) )
46 , mSettings( std::move( other.mSettings ) )
47 , mLegendSize( other.mLegendSize )
49 mProxyModel->setLayerTreeModel( mLegendModel );
54 if ( mProxyModel.get() == model )
57 mProxyModel.reset( model );
58 mProxyModel->setLayerTreeModel( mLegendModel );
65 std::unique_ptr< QgsRenderContext > tmpContext;
72 tmpContext->setRendererScale( mSettings.mapScale() );
73 tmpContext->setMapToPixel(
QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * tmpContext->scaleFactor() ) ) );
75 renderContext = tmpContext.get();
80 return paintAndDetermineSize( *renderContext );
89 context.setRendererScale( mSettings.mapScale() );
90 context.setMapToPixel(
QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * context.scaleFactor() ) ) );
93 paintAndDetermineSize( context );
105 json[u
"title"_s] = mSettings.title();
113 const QList<QgsLayerTreeNode *> childNodes = nodeGroup->
children();
116 if ( !mProxyModel->nodeShown( node ) )
122 const QModelIndex idx = mLegendModel->
node2index( nodeGroup );
123 const QString text = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
126 group[ u
"type"_s ] = u
"group"_s;
127 group[ u
"title"_s ] = text;
128 nodes.append( group );
137 const QModelIndex idx = mLegendModel->node2index( nodeLayer );
138 text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
141 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
143 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
146 if ( legendNodes.count() == 1 )
148 QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
149 group[ u
"type"_s ] = u
"layer"_s;
152 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->
layer() ) )
154 if ( vLayer->renderer() )
157 if ( ! ruleKey.isEmpty() )
160 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
163 group[ u
"rule"_s ] = ruleExp;
169 nodes.append( group );
171 else if ( legendNodes.count() > 1 )
174 group[ u
"type"_s ] = u
"layer"_s;
175 group[ u
"title"_s ] = text;
178 for (
int j = 0; j < legendNodes.count(); j++ )
180 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
184 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->
layer() ) )
186 if ( vLayer->renderer() )
189 if ( ! ruleKey.isEmpty() )
192 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
195 symbol[ u
"rule"_s ] = ruleExp;
201 symbols.append( symbol );
203 group[ u
"symbols"_s ] = symbols;
205 nodes.append( group );
210 json[u
"nodes"_s] = nodes;
217 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
221 mSettings.updateDataDefinedProperties( context );
225 QgsScopedRenderContextPainterSwap noPainter( context,
nullptr );
227 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
229 const int columnCount = setColumns( componentGroups );
231 QMap< int, double > maxColumnWidths;
232 qreal maxEqualColumnWidth = 0;
240 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
242 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
243 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
244 maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
247 if ( columnCount == 1 )
250 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
251 maxColumnWidths[ 0 ] = maxEqualColumnWidth;
255 QSizeF titleSize = drawTitle( context, 0 );
257 titleSize.rwidth() += mSettings.boxSpace() * 2.0;
262 bool firstInColumn =
true;
263 double columnMaxHeight = 0;
264 qreal columnWidth = 0;
266 ColumnContext columnContext;
267 columnContext.left = mSettings.boxSpace();
268 columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
269 double currentY = columnTop;
271 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
273 if ( group.column > column )
276 columnContext.left = group.column > 0 ? ( columnContext.right + mSettings.columnSpace() ) : mSettings.boxSpace();
277 columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
278 columnContext.right = columnContext.left + columnWidth;
279 currentY = columnTop;
281 firstInColumn =
true;
283 if ( !firstInColumn )
285 currentY += spaceAboveGroup( group );
288 drawGroup( group, context, columnContext, currentY );
290 currentY += group.size.height();
291 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
293 firstInColumn =
false;
295 const double totalWidth = columnContext.right + mSettings.boxSpace();
297 size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
298 size.rwidth() = totalWidth;
299 if ( !mSettings.title().isEmpty() )
301 size.rwidth() = std::max( titleSize.width(), size.width() );
305 if ( mLegendSize.isValid() )
307 qreal w = std::max( size.width(), mLegendSize.width() );
308 qreal h = std::max( size.height(), mLegendSize.height() );
309 size = QSizeF( w, h );
313 if ( !mSettings.title().isEmpty() )
315 drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
321void QgsLegendRenderer::widthAndOffsetForTitleText(
const Qt::AlignmentFlag halignment,
const double legendWidth,
double &textBoxWidth,
double &textBoxLeft )
const
323 switch ( halignment )
326 textBoxLeft = mSettings.boxSpace();
327 textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
330 case Qt::AlignHCenter:
333 const double centerX = legendWidth / 2;
334 textBoxWidth = ( std::min(
static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
335 textBoxLeft = centerX - textBoxWidth / 2.;
341QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList(
QgsLayerTreeGroup *parentGroup,
QgsRenderContext &context,
double indent )
343 QList<LegendComponentGroup> componentGroups;
346 return componentGroups;
348 const QList<QgsLayerTreeNode *> childNodes = parentGroup->
children();
349 for ( QgsLayerTreeNode *node : childNodes )
351 if ( !mProxyModel->nodeShown( node ) )
357 QString style = node->customProperty( u
"legend/title-style"_s ).toString();
359 double newIndent = indent;
360 if ( style ==
"subgroup"_L1 )
370 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
372 bool hasSubItems = !subgroups.empty();
376 LegendComponent component;
377 component.item = node;
378 component.indent = newIndent;
379 component.size = drawGroupTitle( nodeGroup, context );
381 if ( !subgroups.isEmpty() )
384 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
386 subgroups[0].components.prepend( component );
387 subgroups[0].size.rheight() += component.size.height();
388 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
389 if ( nodeGroup->
customProperty( u
"legend/column-break"_s ).toInt() )
390 subgroups[0].placeColumnBreakBeforeGroup =
true;
395 LegendComponentGroup group;
396 group.placeColumnBreakBeforeGroup = nodeGroup->
customProperty( u
"legend/column-break"_s ).toInt();
397 group.components.append( component );
398 group.size.rwidth() += component.size.width();
399 group.size.rheight() += component.size.height();
400 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
401 subgroups.append( group );
407 componentGroups.append( subgroups );
415 bool allowColumnSplit =
false;
419 allowColumnSplit = mSettings.splitLayer();
422 allowColumnSplit =
true;
425 allowColumnSplit =
false;
429 LegendComponentGroup group;
430 group.placeColumnBreakBeforeGroup = nodeLayer->
customProperty( u
"legend/column-break"_s ).toInt();
434 LegendComponent component;
435 component.item = node;
436 component.size = drawLayerTitle( nodeLayer, context );
437 component.indent = indent;
438 group.components.append( component );
439 group.size.rwidth() = component.size.width();
440 group.size.rheight() = component.size.height();
443 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
448 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
451 QList<LegendComponentGroup> layerGroups;
452 layerGroups.reserve( legendNodes.count() );
454 bool groupIsLayerGroup =
true;
455 double symbolIndent = indent;
456 switch ( layerStyle )
460 symbolIndent += mSettings.style( layerStyle ).indent( );
465 for (
int j = 0; j < legendNodes.count(); j++ )
467 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
469 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
473 if ( !allowColumnSplit || j == 0 )
477 if ( groupIsLayerGroup )
478 layerGroups.prepend( group );
480 layerGroups.append( group );
482 group = LegendComponentGroup();
483 group.placeColumnBreakBeforeGroup =
true;
484 groupIsLayerGroup =
false;
489 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
491 if ( !group.components.isEmpty() )
496 group.size.rheight() += symbolComponent.size.height();
497 symbolComponent.indent = symbolIndent;
498 group.components.append( symbolComponent );
502 if ( group.size.height() > 0 )
504 if ( groupIsLayerGroup )
505 layerGroups.prepend( group );
507 layerGroups.append( group );
508 group = LegendComponentGroup();
509 groupIsLayerGroup =
false;
511 LegendComponentGroup symbolGroup;
512 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
513 symbolComponent.indent = symbolIndent;
514 symbolGroup.components.append( symbolComponent );
515 symbolGroup.size.rwidth() = symbolComponent.size.width();
516 symbolGroup.size.rheight() = symbolComponent.size.height();
517 layerGroups.append( symbolGroup );
520 if ( group.size.height() > 0 )
522 if ( groupIsLayerGroup )
523 layerGroups.prepend( group );
525 layerGroups.append( group );
527 componentGroups.append( layerGroups );
531 return componentGroups;
535int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
538 double totalHeight = 0;
539 qreal maxGroupHeight = 0;
540 int forcedColumnBreaks = 0;
541 double totalSpaceAboveGroups = 0;
543 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
545 const double topMargin = spaceAboveGroup( group );
546 totalHeight += topMargin;
547 totalSpaceAboveGroups += topMargin;
549 const double groupHeight = group.size.height();
550 totalHeight += groupHeight;
551 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
553 if ( group.placeColumnBreakBeforeGroup )
554 forcedColumnBreaks++;
556 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
557 double averageGroupHeight = totalGroupHeight / componentGroups.size();
559 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
564 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
565 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
572 double maxColumnHeight = 0;
573 int currentColumn = 0;
574 int currentColumnGroupCount = 0;
575 double currentColumnHeight = 0;
576 int autoPlacedBreaks = 0;
579 double averageSpaceAboveGroups = 0;
580 if ( componentGroups.size() > targetNumberColumns )
581 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
583 double totalRemainingGroupHeight = totalGroupHeight;
584 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
585 for (
int i = 0; i < componentGroups.size(); i++ )
587 const LegendComponentGroup &group = componentGroups.at( i );
588 const double currentGroupHeight = group.size.height();
589 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
591 totalRemainingGroupHeight -= currentGroupHeight;
592 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
594 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
595 if ( currentColumnGroupCount > 0 )
596 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
597 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
599 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
600 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
601 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
603 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
604 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
605 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight
606 + totalRemainingSpaceAboveGroups
607 - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
609 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded
610 + estimatedRemainingTotalHeightAfterThisGroup;
613 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
617 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
619 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
621 bool canCreateNewColumn = ( currentColumnGroupCount > 0 )
622 && ( currentColumn < targetNumberColumns - 1 )
623 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
625 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne
626 && currentColumnGroupCount > 0
627 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight
628 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight;
630 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
631 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
635 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
637 if ( canCreateNewColumn && shouldCreateNewColumn )
641 if ( !group.placeColumnBreakBeforeGroup )
643 currentColumnGroupCount = 0;
644 currentColumnHeight = group.size.height();
648 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
650 componentGroups[i].column = currentColumn;
651 currentColumnGroupCount++;
652 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
655 auto refineColumns = [&componentGroups,
this]() ->
bool
657 QHash< int, double > columnHeights;
658 QHash< int, int > columnGroupCounts;
659 double currentColumnHeight = 0;
660 int currentColumn = -1;
663 double maxCurrentColumnHeight = 0;
664 for (
int i = 0; i < componentGroups.size(); i++ )
666 const LegendComponentGroup &group = componentGroups.at( i );
667 if ( group.column != currentColumn )
669 if ( currentColumn >= 0 )
671 columnHeights.insert( currentColumn, currentColumnHeight );
672 columnGroupCounts.insert( currentColumn, groupCount );
675 currentColumn = group.column;
676 currentColumnHeight = 0;
678 columnCount = std::max( columnCount, currentColumn + 1 );
681 const double spaceAbove = spaceAboveGroup( group );
682 currentColumnHeight += spaceAbove + group.size.height();
685 columnHeights.insert( currentColumn, currentColumnHeight );
686 columnGroupCounts.insert( currentColumn, groupCount );
688 double totalColumnHeights = 0;
689 for (
int i = 0; i < columnCount; ++ i )
691 totalColumnHeights += columnHeights[i];
692 maxCurrentColumnHeight = std::max( maxCurrentColumnHeight, columnHeights[i] );
695 const double averageColumnHeight = totalColumnHeights / columnCount;
697 bool changed =
false;
698 int nextCandidateColumnForShift = 1;
699 for (
int i = 0; i < componentGroups.size(); i++ )
701 LegendComponentGroup &group = componentGroups[ i ];
702 if ( group.column < nextCandidateColumnForShift )
706 const bool canShift = !group.placeColumnBreakBeforeGroup
707 && columnGroupCounts[ group.column ] >= 2;
710 && columnHeights[ group.column - 1 ] < averageColumnHeight
711 && ( columnHeights[ group.column - 1 ] + group.size.height() ) * 0.9 < maxCurrentColumnHeight
715 columnHeights[ group.column ] += group.size.height() + spaceAboveGroup( group );
716 columnGroupCounts[ group.column ]++;
717 columnHeights[ group.column + 1 ] -= group.size.height();
718 columnGroupCounts[ group.column + 1]--;
723 nextCandidateColumnForShift = group.column + 1;
729 bool wasRefined =
true;
731 while ( wasRefined && iterations < 2 )
733 wasRefined = refineColumns();
738 QMap<QString, qreal> maxSymbolWidth;
739 for (
int i = 0; i < componentGroups.size(); i++ )
741 LegendComponentGroup &group = componentGroups[i];
742 for (
int j = 0; j < group.components.size(); j++ )
744 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
746 QString key = u
"%1-%2"_s.arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
747 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
751 for (
int i = 0; i < componentGroups.size(); i++ )
753 LegendComponentGroup &group = componentGroups[i];
754 for (
int j = 0; j < group.components.size(); j++ )
756 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
758 QString key = u
"%1-%2"_s.arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
761 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
762 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
763 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
767 return targetNumberColumns;
770QSizeF QgsLegendRenderer::drawTitle(
QgsRenderContext &context,
double top, Qt::AlignmentFlag halignment,
double legendWidth )
const
773 if ( mSettings.title().isEmpty() )
778 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
783 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
788 double overallTextHeight = 0;
789 double overallTextWidth = 0;
792 QgsScopedRenderContextScaleToPixels contextToPixels( context );
797 size.rheight() = overallTextHeight / dotsPerMM;
798 size.rwidth() = overallTextWidth / dotsPerMM;
802 QgsScopedRenderContextScaleToPixels contextToPixels( context );
804 const QRectF r( textBoxLeft * dotsPerMM, top * dotsPerMM, textBoxWidth * dotsPerMM, overallTextHeight );
816double QgsLegendRenderer::spaceAboveGroup(
const LegendComponentGroup &group )
818 if ( group.components.isEmpty() )
return 0;
820 LegendComponent component = group.components.first();
822 if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
826 else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
830 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
839QSizeF QgsLegendRenderer::drawGroup(
const LegendComponentGroup &group,
QgsRenderContext &context, ColumnContext columnContext,
double top )
842 QSizeF size = QSizeF( group.size );
843 double currentY = top;
844 for (
const LegendComponent &component : std::as_const( group.components ) )
846 if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
856 ColumnContext columnContextForItem = columnContext;
857 double indentWidth = component.indent;
870 columnContextForItem.left += indentWidth;
874 columnContextForItem.right -= indentWidth;
876 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
877 size.rwidth() = std::max( groupSize.width(), size.width() );
880 else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
891 ColumnContext columnContextForItem = columnContext;
892 double indentWidth = component.indent;
893 columnContextForItem.left += indentWidth;
894 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
895 size.rwidth() = std::max( subGroupSize.width(), size.width() );
898 else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
905 ColumnContext columnContextForItem = columnContext;
906 double indentWidth = 0;
907 indentWidth = component.indent;
910 columnContextForItem.left += indentWidth;
914 columnContextForItem.right -= indentWidth;
917 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
919 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
921 currentY += component.size.height();
929 QgsLayerTreeModelLegendNode::ItemContext ctx;
933 QgsExpressionContextScope *layerScope =
nullptr;
942 ctx.
point = QPointF( columnContext.left, top );
951 switch ( mSettings.symbolAlignment() )
965 QgsExpressionContextScope *symbolScope =
nullptr;
966 if (
const QgsSymbolLegendNode *symbolNode =
dynamic_cast< const QgsSymbolLegendNode *
>( symbolItem ) )
968 symbolScope = symbolNode->createSymbolScope();
975 QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->
draw( mSettings, ctx );
983 LegendComponent component;
984 component.item = symbolItem;
991 double width = std::max(
static_cast< double >( im.
symbolSize.width() ), maxSiblingSymbolWidth )
998 component.size = QSizeF( width, height );
1004 QSizeF size( 0, 0 );
1005 QModelIndex idx = mLegendModel->node2index( nodeLayer );
1006 QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
1008 if ( titleString.isEmpty() )
1011 const QgsTextFormat layerFormat = mSettings.style(
nodeLegendStyle( nodeLayer ) ).textFormat();
1013 QgsExpressionContextScope *layerScope =
nullptr;
1014 if ( nodeLayer->
layer() )
1020 const QStringList lines = mSettings.evaluateItemText( titleString, context.
expressionContext() );
1024 double overallTextHeight = 0;
1025 double overallTextWidth = 0;
1027 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1033 size.rheight() = overallTextHeight / dotsPerMM;
1034 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1035 ( mSettings.style(
nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1039 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1058 QSizeF size( 0, 0 );
1059 QModelIndex idx = mLegendModel->node2index( nodeGroup );
1061 const QgsTextFormat groupFormat = mSettings.style(
nodeLegendStyle( nodeGroup ) ).textFormat();
1063 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.
expressionContext() );
1065 double overallTextHeight = 0;
1066 double overallTextWidth = 0;
1069 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1077 size.rheight() = overallTextHeight / dotsPerMM;
1078 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1079 ( mSettings.style(
nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1083 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1099 QString style = node->
customProperty( u
"legend/title-style"_s ).toString();
1100 if ( style ==
"hidden"_L1 )
1102 else if ( style ==
"group"_L1 )
1104 else if ( style ==
"subgroup"_L1 )
1127 return mProxyModel.get();
1142 str = u
"subgroup"_s;
1148 if ( !str.isEmpty() )
1156 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.
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
TextHorizontalAlignment
Text horizontal alignment.
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.
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.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
#define 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.