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 );
414 bool allowColumnSplit =
false;
418 allowColumnSplit = mSettings.splitLayer();
421 allowColumnSplit =
true;
424 allowColumnSplit =
false;
428 LegendComponentGroup group;
429 group.placeColumnBreakBeforeGroup = nodeLayer->
customProperty( u
"legend/column-break"_s ).toInt();
433 LegendComponent component;
434 component.item = node;
435 component.size = drawLayerTitle( nodeLayer, context );
436 component.indent = indent;
437 group.components.append( component );
438 group.size.rwidth() = component.size.width();
439 group.size.rheight() = component.size.height();
442 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
447 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
450 QList<LegendComponentGroup> layerGroups;
451 layerGroups.reserve( legendNodes.count() );
453 bool groupIsLayerGroup =
true;
454 double symbolIndent = indent;
455 switch ( layerStyle )
459 symbolIndent += mSettings.style( layerStyle ).indent();
464 for (
int j = 0; j < legendNodes.count(); j++ )
466 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
468 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
472 if ( !allowColumnSplit || j == 0 )
476 if ( groupIsLayerGroup )
477 layerGroups.prepend( group );
479 layerGroups.append( group );
481 group = LegendComponentGroup();
482 group.placeColumnBreakBeforeGroup =
true;
483 groupIsLayerGroup =
false;
488 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
490 if ( !group.components.isEmpty() )
495 group.size.rheight() += symbolComponent.size.height();
496 symbolComponent.indent = symbolIndent;
497 group.components.append( symbolComponent );
501 if ( group.size.height() > 0 )
503 if ( groupIsLayerGroup )
504 layerGroups.prepend( group );
506 layerGroups.append( group );
507 group = LegendComponentGroup();
508 groupIsLayerGroup =
false;
510 LegendComponentGroup symbolGroup;
511 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
512 symbolComponent.indent = symbolIndent;
513 symbolGroup.components.append( symbolComponent );
514 symbolGroup.size.rwidth() = symbolComponent.size.width();
515 symbolGroup.size.rheight() = symbolComponent.size.height();
516 layerGroups.append( symbolGroup );
519 if ( group.size.height() > 0 )
521 if ( groupIsLayerGroup )
522 layerGroups.prepend( group );
524 layerGroups.append( group );
526 componentGroups.append( layerGroups );
530 return componentGroups;
534int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
537 double totalHeight = 0;
538 qreal maxGroupHeight = 0;
539 int forcedColumnBreaks = 0;
540 double totalSpaceAboveGroups = 0;
542 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
544 const double topMargin = spaceAboveGroup( group );
545 totalHeight += topMargin;
546 totalSpaceAboveGroups += topMargin;
548 const double groupHeight = group.size.height();
549 totalHeight += groupHeight;
550 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
552 if ( group.placeColumnBreakBeforeGroup )
553 forcedColumnBreaks++;
555 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
556 double averageGroupHeight = totalGroupHeight / componentGroups.size();
558 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
563 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
564 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
571 double maxColumnHeight = 0;
572 int currentColumn = 0;
573 int currentColumnGroupCount = 0;
574 double currentColumnHeight = 0;
575 int autoPlacedBreaks = 0;
578 double averageSpaceAboveGroups = 0;
579 if ( componentGroups.size() > targetNumberColumns )
580 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
582 double totalRemainingGroupHeight = totalGroupHeight;
583 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
584 for (
int i = 0; i < componentGroups.size(); i++ )
586 const LegendComponentGroup &group = componentGroups.at( i );
587 const double currentGroupHeight = group.size.height();
588 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
590 totalRemainingGroupHeight -= currentGroupHeight;
591 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
593 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
594 if ( currentColumnGroupCount > 0 )
595 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
596 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
598 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
599 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
600 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
602 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
603 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
604 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight + totalRemainingSpaceAboveGroups - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
606 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded + estimatedRemainingTotalHeightAfterThisGroup;
609 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
613 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
615 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
617 bool canCreateNewColumn = ( currentColumnGroupCount > 0 )
618 && ( currentColumn < targetNumberColumns - 1 )
619 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
621 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne
622 && currentColumnGroupCount > 0
623 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight
624 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight;
626 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
627 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
631 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
633 if ( canCreateNewColumn && shouldCreateNewColumn )
637 if ( !group.placeColumnBreakBeforeGroup )
639 currentColumnGroupCount = 0;
640 currentColumnHeight = group.size.height();
644 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
646 componentGroups[i].column = currentColumn;
647 currentColumnGroupCount++;
648 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
651 auto refineColumns = [&componentGroups,
this]() ->
bool {
652 QHash< int, double > columnHeights;
653 QHash< int, int > columnGroupCounts;
654 double currentColumnHeight = 0;
655 int currentColumn = -1;
658 double maxCurrentColumnHeight = 0;
659 for (
int i = 0; i < componentGroups.size(); i++ )
661 const LegendComponentGroup &group = componentGroups.at( i );
662 if ( group.column != currentColumn )
664 if ( currentColumn >= 0 )
666 columnHeights.insert( currentColumn, currentColumnHeight );
667 columnGroupCounts.insert( currentColumn, groupCount );
670 currentColumn = group.column;
671 currentColumnHeight = 0;
673 columnCount = std::max( columnCount, currentColumn + 1 );
676 const double spaceAbove = spaceAboveGroup( group );
677 currentColumnHeight += spaceAbove + group.size.height();
680 columnHeights.insert( currentColumn, currentColumnHeight );
681 columnGroupCounts.insert( currentColumn, groupCount );
683 double totalColumnHeights = 0;
684 for (
int i = 0; i < columnCount; ++i )
686 totalColumnHeights += columnHeights[i];
687 maxCurrentColumnHeight = std::max( maxCurrentColumnHeight, columnHeights[i] );
690 const double averageColumnHeight = totalColumnHeights / columnCount;
692 bool changed =
false;
693 int nextCandidateColumnForShift = 1;
694 for (
int i = 0; i < componentGroups.size(); i++ )
696 LegendComponentGroup &group = componentGroups[i];
697 if ( group.column < nextCandidateColumnForShift )
701 const bool canShift = !group.placeColumnBreakBeforeGroup && columnGroupCounts[group.column] >= 2;
703 if ( canShift && columnHeights[group.column - 1] < averageColumnHeight && ( columnHeights[group.column - 1] + group.size.height() ) * 0.9 < maxCurrentColumnHeight )
706 columnHeights[group.column] += group.size.height() + spaceAboveGroup( group );
707 columnGroupCounts[group.column]++;
708 columnHeights[group.column + 1] -= group.size.height();
709 columnGroupCounts[group.column + 1]--;
714 nextCandidateColumnForShift = group.column + 1;
720 bool wasRefined =
true;
722 while ( wasRefined && iterations < 2 )
724 wasRefined = refineColumns();
729 QMap<QString, qreal> maxSymbolWidth;
730 for (
int i = 0; i < componentGroups.size(); i++ )
732 LegendComponentGroup &group = componentGroups[i];
733 for (
int j = 0; j < group.components.size(); j++ )
735 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
737 QString key = u
"%1-%2"_s.arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
738 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
742 for (
int i = 0; i < componentGroups.size(); i++ )
744 LegendComponentGroup &group = componentGroups[i];
745 for (
int j = 0; j < group.components.size(); j++ )
747 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
749 QString key = u
"%1-%2"_s.arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
751 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
752 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
753 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
757 return targetNumberColumns;
760QSizeF QgsLegendRenderer::drawTitle(
QgsRenderContext &context,
double top, Qt::AlignmentFlag halignment,
double legendWidth )
const
763 if ( mSettings.title().isEmpty() )
768 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
773 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
778 double overallTextHeight = 0;
779 double overallTextWidth = 0;
782 QgsScopedRenderContextScaleToPixels contextToPixels( context );
787 size.rheight() = overallTextHeight / dotsPerMM;
788 size.rwidth() = overallTextWidth / dotsPerMM;
792 QgsScopedRenderContextScaleToPixels contextToPixels( context );
794 const QRectF r( textBoxLeft * dotsPerMM, top * dotsPerMM, textBoxWidth * dotsPerMM, overallTextHeight );
807double QgsLegendRenderer::spaceAboveGroup(
const LegendComponentGroup &group )
809 if ( group.components.isEmpty() )
812 LegendComponent component = group.components.first();
814 if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
818 else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
822 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
831QSizeF QgsLegendRenderer::drawGroup(
const LegendComponentGroup &group,
QgsRenderContext &context, ColumnContext columnContext,
double top )
834 QSizeF size = QSizeF( group.size );
835 double currentY = top;
836 for (
const LegendComponent &component : std::as_const( group.components ) )
838 if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
848 ColumnContext columnContextForItem = columnContext;
849 double indentWidth = component.indent;
862 columnContextForItem.left += indentWidth;
866 columnContextForItem.right -= indentWidth;
868 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
869 size.rwidth() = std::max( groupSize.width(), size.width() );
872 else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
883 ColumnContext columnContextForItem = columnContext;
884 double indentWidth = component.indent;
885 columnContextForItem.left += indentWidth;
886 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
887 size.rwidth() = std::max( subGroupSize.width(), size.width() );
890 else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
897 ColumnContext columnContextForItem = columnContext;
898 double indentWidth = 0;
899 indentWidth = component.indent;
902 columnContextForItem.left += indentWidth;
906 columnContextForItem.right -= indentWidth;
909 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
911 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
913 currentY += component.size.height();
921 QgsLayerTreeModelLegendNode::ItemContext ctx;
925 QgsExpressionContextScope *layerScope =
nullptr;
934 ctx.
point = QPointF( columnContext.left, top );
943 switch ( mSettings.symbolAlignment() )
957 QgsExpressionContextScope *symbolScope =
nullptr;
958 if (
const QgsSymbolLegendNode *symbolNode =
dynamic_cast< const QgsSymbolLegendNode *
>( symbolItem ) )
960 symbolScope = symbolNode->createSymbolScope();
967 QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->
draw( mSettings, ctx );
975 LegendComponent component;
976 component.item = symbolItem;
983 double width = std::max(
static_cast< double >( im.
symbolSize.width() ), maxSiblingSymbolWidth )
990 component.size = QSizeF( width, height );
997 QModelIndex idx = mLegendModel->node2index( nodeLayer );
998 QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
1000 if ( titleString.isEmpty() )
1003 const QgsTextFormat layerFormat = mSettings.style(
nodeLegendStyle( nodeLayer ) ).textFormat();
1005 QgsExpressionContextScope *layerScope =
nullptr;
1006 if ( nodeLayer->
layer() )
1012 const QStringList lines = mSettings.evaluateItemText( titleString, context.
expressionContext() );
1016 double overallTextHeight = 0;
1017 double overallTextWidth = 0;
1019 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1025 size.rheight() = overallTextHeight / dotsPerMM;
1026 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin * ( mSettings.style(
nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1030 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1039 overallTextHeight );
1053 QSizeF size( 0, 0 );
1054 QModelIndex idx = mLegendModel->node2index( nodeGroup );
1056 const QgsTextFormat groupFormat = mSettings.style(
nodeLegendStyle( nodeGroup ) ).textFormat();
1058 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.
expressionContext() );
1060 double overallTextHeight = 0;
1061 double overallTextWidth = 0;
1064 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1072 size.rheight() = overallTextHeight / dotsPerMM;
1073 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin * ( mSettings.style(
nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1077 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1087 overallTextHeight );
1097 QString style = node->
customProperty( u
"legend/title-style"_s ).toString();
1098 if ( style ==
"hidden"_L1 )
1100 else if ( style ==
"group"_L1 )
1102 else if ( style ==
"subgroup"_L1 )
1125 return mProxyModel.get();
1140 str = u
"subgroup"_s;
1146 if ( !str.isEmpty() )
1154 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.