33 : mLegendModel( legendModel )
35 , mSettings( settings )
37 mProxyModel->setLayerTreeModel( mLegendModel );
41 : mLegendModel( other.mLegendModel )
42 , mProxyModel( std::move( other.mProxyModel ) )
43 , mSettings( std::move( other.mSettings ) )
44 , mLegendSize( other.mLegendSize )
46 mProxyModel->setLayerTreeModel( mLegendModel );
51 if ( mProxyModel.get() == model )
54 mProxyModel.reset( model );
55 mProxyModel->setLayerTreeModel( mLegendModel );
62 std::unique_ptr< QgsRenderContext > tmpContext;
69 tmpContext->setRendererScale( mSettings.mapScale() );
70 tmpContext->setMapToPixel(
QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * tmpContext->scaleFactor() ) ) );
72 renderContext = tmpContext.get();
77 return paintAndDetermineSize( *renderContext );
86 context.setRendererScale( mSettings.mapScale() );
87 context.setMapToPixel(
QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * context.scaleFactor() ) ) );
90 paintAndDetermineSize( context );
102 json[QStringLiteral(
"title" )] = mSettings.title();
110 const QList<QgsLayerTreeNode *> childNodes = nodeGroup->
children();
113 if ( !mProxyModel->nodeShown( node ) )
119 const QModelIndex idx = mLegendModel->
node2index( nodeGroup );
120 const QString text = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
123 group[ QStringLiteral(
"type" ) ] = QStringLiteral(
"group" );
124 group[ QStringLiteral(
"title" ) ] = text;
125 nodes.append( group );
134 const QModelIndex idx = mLegendModel->node2index( nodeLayer );
135 text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
138 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
140 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
143 if ( legendNodes.count() == 1 )
145 QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
146 group[ QStringLiteral(
"type" ) ] = QStringLiteral(
"layer" );
149 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->
layer() ) )
151 if ( vLayer->renderer() )
154 if ( ! ruleKey.isEmpty() )
157 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
160 group[ QStringLiteral(
"rule" ) ] = ruleExp;
166 nodes.append( group );
168 else if ( legendNodes.count() > 1 )
171 group[ QStringLiteral(
"type" ) ] = QStringLiteral(
"layer" );
172 group[ QStringLiteral(
"title" ) ] = text;
175 for (
int j = 0; j < legendNodes.count(); j++ )
177 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
181 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->
layer() ) )
183 if ( vLayer->renderer() )
186 if ( ! ruleKey.isEmpty() )
189 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
192 symbol[ QStringLiteral(
"rule" ) ] = ruleExp;
198 symbols.append( symbol );
200 group[ QStringLiteral(
"symbols" ) ] = symbols;
202 nodes.append( group );
207 json[QStringLiteral(
"nodes" )] = nodes;
214 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
218 mSettings.updateDataDefinedProperties( context );
222 QgsScopedRenderContextPainterSwap noPainter( context,
nullptr );
224 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
226 const int columnCount = setColumns( componentGroups );
228 QMap< int, double > maxColumnWidths;
229 qreal maxEqualColumnWidth = 0;
237 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
239 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
240 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
241 maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
244 if ( columnCount == 1 )
247 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
248 maxColumnWidths[ 0 ] = maxEqualColumnWidth;
252 QSizeF titleSize = drawTitle( context, 0 );
254 titleSize.rwidth() += mSettings.boxSpace() * 2.0;
259 bool firstInColumn =
true;
260 double columnMaxHeight = 0;
261 qreal columnWidth = 0;
263 ColumnContext columnContext;
264 columnContext.left = mSettings.boxSpace();
265 columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
266 double currentY = columnTop;
268 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
270 if ( group.column > column )
273 columnContext.left = group.column > 0 ? ( columnContext.right + mSettings.columnSpace() ) : mSettings.boxSpace();
274 columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
275 columnContext.right = columnContext.left + columnWidth;
276 currentY = columnTop;
278 firstInColumn =
true;
280 if ( !firstInColumn )
282 currentY += spaceAboveGroup( group );
285 drawGroup( group, context, columnContext, currentY );
287 currentY += group.size.height();
288 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
290 firstInColumn =
false;
292 const double totalWidth = columnContext.right + mSettings.boxSpace();
294 size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
295 size.rwidth() = totalWidth;
296 if ( !mSettings.title().isEmpty() )
298 size.rwidth() = std::max( titleSize.width(), size.width() );
302 if ( mLegendSize.isValid() )
304 qreal w = std::max( size.width(), mLegendSize.width() );
305 qreal h = std::max( size.height(), mLegendSize.height() );
306 size = QSizeF( w, h );
310 if ( !mSettings.title().isEmpty() )
312 drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
318void QgsLegendRenderer::widthAndOffsetForTitleText(
const Qt::AlignmentFlag halignment,
const double legendWidth,
double &textBoxWidth,
double &textBoxLeft )
const
320 switch ( halignment )
323 textBoxLeft = mSettings.boxSpace();
324 textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
327 case Qt::AlignHCenter:
330 const double centerX = legendWidth / 2;
331 textBoxWidth = ( std::min(
static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
332 textBoxLeft = centerX - textBoxWidth / 2.;
338QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList(
QgsLayerTreeGroup *parentGroup,
QgsRenderContext &context,
double indent )
340 QList<LegendComponentGroup> componentGroups;
343 return componentGroups;
345 const QList<QgsLayerTreeNode *> childNodes = parentGroup->
children();
346 for ( QgsLayerTreeNode *node : childNodes )
348 if ( !mProxyModel->nodeShown( node ) )
354 QString style = node->customProperty( QStringLiteral(
"legend/title-style" ) ).toString();
356 double newIndent = indent;
357 if ( style == QLatin1String(
"subgroup" ) )
367 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
369 bool hasSubItems = !subgroups.empty();
373 LegendComponent component;
374 component.item = node;
375 component.indent = newIndent;
376 component.size = drawGroupTitle( nodeGroup, context );
378 if ( !subgroups.isEmpty() )
381 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
383 subgroups[0].components.prepend( component );
384 subgroups[0].size.rheight() += component.size.height();
385 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
386 if ( nodeGroup->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt() )
387 subgroups[0].placeColumnBreakBeforeGroup =
true;
392 LegendComponentGroup group;
393 group.placeColumnBreakBeforeGroup = nodeGroup->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt();
394 group.components.append( component );
395 group.size.rwidth() += component.size.width();
396 group.size.rheight() += component.size.height();
397 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
398 subgroups.append( group );
404 componentGroups.append( subgroups );
412 bool allowColumnSplit =
false;
416 allowColumnSplit = mSettings.splitLayer();
419 allowColumnSplit =
true;
422 allowColumnSplit =
false;
426 LegendComponentGroup group;
427 group.placeColumnBreakBeforeGroup = nodeLayer->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt();
431 LegendComponent component;
432 component.item = node;
433 component.size = drawLayerTitle( nodeLayer, context );
434 component.indent = indent;
435 group.components.append( component );
436 group.size.rwidth() = component.size.width();
437 group.size.rheight() = component.size.height();
440 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
445 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
448 QList<LegendComponentGroup> layerGroups;
449 layerGroups.reserve( legendNodes.count() );
451 bool groupIsLayerGroup =
true;
452 double symbolIndent = indent;
453 switch ( layerStyle )
457 symbolIndent += mSettings.style( layerStyle ).indent( );
462 for (
int j = 0; j < legendNodes.count(); j++ )
464 QgsLayerTreeModelLegendNode *
legendNode = legendNodes.at( j );
466 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
470 if ( !allowColumnSplit || j == 0 )
474 if ( groupIsLayerGroup )
475 layerGroups.prepend( group );
477 layerGroups.append( group );
479 group = LegendComponentGroup();
480 group.placeColumnBreakBeforeGroup =
true;
481 groupIsLayerGroup =
false;
486 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
488 if ( !group.components.isEmpty() )
493 group.size.rheight() += symbolComponent.size.height();
494 symbolComponent.indent = symbolIndent;
495 group.components.append( symbolComponent );
499 if ( group.size.height() > 0 )
501 if ( groupIsLayerGroup )
502 layerGroups.prepend( group );
504 layerGroups.append( group );
505 group = LegendComponentGroup();
506 groupIsLayerGroup =
false;
508 LegendComponentGroup symbolGroup;
509 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
510 symbolComponent.indent = symbolIndent;
511 symbolGroup.components.append( symbolComponent );
512 symbolGroup.size.rwidth() = symbolComponent.size.width();
513 symbolGroup.size.rheight() = symbolComponent.size.height();
514 layerGroups.append( symbolGroup );
517 if ( group.size.height() > 0 )
519 if ( groupIsLayerGroup )
520 layerGroups.prepend( group );
522 layerGroups.append( group );
524 componentGroups.append( layerGroups );
528 return componentGroups;
532int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
535 double totalHeight = 0;
536 qreal maxGroupHeight = 0;
537 int forcedColumnBreaks = 0;
538 double totalSpaceAboveGroups = 0;
540 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
542 const double topMargin = spaceAboveGroup( group );
543 totalHeight += topMargin;
544 totalSpaceAboveGroups += topMargin;
546 const double groupHeight = group.size.height();
547 totalHeight += groupHeight;
548 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
550 if ( group.placeColumnBreakBeforeGroup )
551 forcedColumnBreaks++;
553 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
554 double averageGroupHeight = totalGroupHeight / componentGroups.size();
556 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
561 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
562 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
569 double maxColumnHeight = 0;
570 int currentColumn = 0;
571 int currentColumnGroupCount = 0;
572 double currentColumnHeight = 0;
573 int autoPlacedBreaks = 0;
576 double averageSpaceAboveGroups = 0;
577 if ( componentGroups.size() > targetNumberColumns )
578 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
580 double totalRemainingGroupHeight = totalGroupHeight;
581 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
582 for (
int i = 0; i < componentGroups.size(); i++ )
584 const LegendComponentGroup &group = componentGroups.at( i );
585 const double currentGroupHeight = group.size.height();
586 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
588 totalRemainingGroupHeight -= currentGroupHeight;
589 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
591 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
592 if ( currentColumnGroupCount > 0 )
593 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
594 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
596 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
597 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
598 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
600 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
601 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
602 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight
603 + totalRemainingSpaceAboveGroups
604 - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
606 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded
607 + estimatedRemainingTotalHeightAfterThisGroup;
610 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
614 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
616 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
618 bool canCreateNewColumn = ( currentColumnGroupCount > 0 )
619 && ( currentColumn < targetNumberColumns - 1 )
620 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
622 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne
623 && currentColumnGroupCount > 0
624 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight
625 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight;
627 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
628 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
632 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
634 if ( canCreateNewColumn && shouldCreateNewColumn )
638 if ( !group.placeColumnBreakBeforeGroup )
640 currentColumnGroupCount = 0;
641 currentColumnHeight = group.size.height();
645 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
647 componentGroups[i].column = currentColumn;
648 currentColumnGroupCount++;
649 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
652 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
704 && columnGroupCounts[ group.column ] >= 2;
707 && columnHeights[ group.column - 1 ] < averageColumnHeight
708 && ( columnHeights[ group.column - 1 ] + group.size.height() ) * 0.9 < maxCurrentColumnHeight
712 columnHeights[ group.column ] += group.size.height() + spaceAboveGroup( group );
713 columnGroupCounts[ group.column ]++;
714 columnHeights[ group.column + 1 ] -= group.size.height();
715 columnGroupCounts[ group.column + 1]--;
720 nextCandidateColumnForShift = group.column + 1;
726 bool wasRefined =
true;
728 while ( wasRefined && iterations < 2 )
730 wasRefined = refineColumns();
735 QMap<QString, qreal> maxSymbolWidth;
736 for (
int i = 0; i < componentGroups.size(); i++ )
738 LegendComponentGroup &group = componentGroups[i];
739 for (
int j = 0; j < group.components.size(); j++ )
741 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
743 QString key = QStringLiteral(
"%1-%2" ).arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
744 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
748 for (
int i = 0; i < componentGroups.size(); i++ )
750 LegendComponentGroup &group = componentGroups[i];
751 for (
int j = 0; j < group.components.size(); j++ )
753 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
755 QString key = QStringLiteral(
"%1-%2" ).arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
758 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
759 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
760 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
764 return targetNumberColumns;
767QSizeF QgsLegendRenderer::drawTitle(
QgsRenderContext &context,
double top, Qt::AlignmentFlag halignment,
double legendWidth )
const
770 if ( mSettings.title().isEmpty() )
775 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
780 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
785 double overallTextHeight = 0;
786 double overallTextWidth = 0;
789 QgsScopedRenderContextScaleToPixels contextToPixels( context );
794 size.rheight() = overallTextHeight / dotsPerMM;
795 size.rwidth() = overallTextWidth / dotsPerMM;
799 QgsScopedRenderContextScaleToPixels contextToPixels( context );
801 const QRectF r( textBoxLeft * dotsPerMM, top * dotsPerMM, textBoxWidth * dotsPerMM, overallTextHeight );
813double QgsLegendRenderer::spaceAboveGroup(
const LegendComponentGroup &group )
815 if ( group.components.isEmpty() )
return 0;
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() )
1008 const QgsTextFormat layerFormat = mSettings.style(
nodeLegendStyle( nodeLayer ) ).textFormat();
1010 QgsExpressionContextScope *layerScope =
nullptr;
1011 if ( nodeLayer->
layer() )
1017 const QStringList lines = mSettings.evaluateItemText( titleString, context.
expressionContext() );
1021 double overallTextHeight = 0;
1022 double overallTextWidth = 0;
1024 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1030 size.rheight() = overallTextHeight / dotsPerMM;
1031 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1032 ( mSettings.style(
nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1036 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1055 QSizeF size( 0, 0 );
1056 QModelIndex idx = mLegendModel->node2index( nodeGroup );
1058 const QgsTextFormat groupFormat = mSettings.style(
nodeLegendStyle( nodeGroup ) ).textFormat();
1060 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.
expressionContext() );
1062 double overallTextHeight = 0;
1063 double overallTextWidth = 0;
1066 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1074 size.rheight() = overallTextHeight / dotsPerMM;
1075 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1076 ( mSettings.style(
nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1080 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1096 QString style = node->
customProperty( QStringLiteral(
"legend/title-style" ) ).toString();
1097 if ( style == QLatin1String(
"hidden" ) )
1099 else if ( style == QLatin1String(
"group" ) )
1101 else if ( style == QLatin1String(
"subgroup" ) )
1124 return mProxyModel.get();
1133 str = QStringLiteral(
"hidden" );
1136 str = QStringLiteral(
"group" );
1139 str = QStringLiteral(
"subgroup" );
1145 if ( !str.isEmpty() )
1153 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.