28 #include <QJsonObject>
35 , mSettings( settings )
41 std::unique_ptr< QgsRenderContext > tmpContext;
48 tmpContext->setRendererScale( mSettings.
mapScale() );
50 renderContext = tmpContext.get();
55 return paintAndDetermineSize( *renderContext );
64 context.setRendererScale( mSettings.
mapScale() );
68 paintAndDetermineSize( context );
80 json[QStringLiteral(
"title" )] = mSettings.
title();
88 const QList<QgsLayerTreeNode *> childNodes = nodeGroup->
children();
94 const QModelIndex idx = mLegendModel->
node2index( nodeGroup );
95 const QString text = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
98 group[ QStringLiteral(
"type" ) ] = QStringLiteral(
"group" );
99 group[ QStringLiteral(
"title" ) ] = text;
100 nodes.append( group );
109 const QModelIndex idx = mLegendModel->
node2index( nodeLayer );
110 text = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
113 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->
layerLegendNodes( nodeLayer );
118 if ( legendNodes.count() == 1 )
120 QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
121 group[ QStringLiteral(
"type" ) ] = QStringLiteral(
"layer" );
122 nodes.append( group );
124 else if ( legendNodes.count() > 1 )
127 group[ QStringLiteral(
"type" ) ] = QStringLiteral(
"layer" );
128 group[ QStringLiteral(
"title" ) ] = text;
131 for (
int j = 0; j < legendNodes.count(); j++ )
135 symbols.append( symbol );
137 group[ QStringLiteral(
"symbols" ) ] = symbols;
139 nodes.append( group );
144 json[QStringLiteral(
"nodes" )] = nodes;
148 QSizeF QgsLegendRenderer::paintAndDetermineSize(
QgsRenderContext &context )
159 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
161 const int columnCount = setColumns( componentGroups );
163 QMap< int, double > maxColumnWidths;
164 qreal maxEqualColumnWidth = 0;
172 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
174 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
175 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
176 maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
179 if ( columnCount == 1 )
182 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.
boxSpace() );
183 maxColumnWidths[ 0 ] = maxEqualColumnWidth;
187 QSizeF titleSize = drawTitle( context, 0 );
189 titleSize.rwidth() += mSettings.
boxSpace() * 2.0;
194 bool firstInColumn =
true;
195 double columnMaxHeight = 0;
196 qreal columnWidth = 0;
198 ColumnContext columnContext;
199 columnContext.left = mSettings.
boxSpace();
200 columnContext.right = std::max( mLegendSize.width() - mSettings.
boxSpace(), mSettings.
boxSpace() );
201 double currentY = columnTop;
203 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
205 if ( group.column > column )
208 columnContext.left = group.column > 0 ? columnContext.right + mSettings.
columnSpace() : mSettings.
boxSpace();
209 columnWidth = mSettings.
equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
210 columnContext.right = columnContext.left + columnWidth;
211 currentY = columnTop;
213 firstInColumn =
true;
215 if ( !firstInColumn )
217 currentY += spaceAboveGroup( group );
220 drawGroup( group, context, columnContext, currentY );
222 currentY += group.size.height();
223 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
225 firstInColumn =
false;
227 const double totalWidth = columnContext.right + mSettings.
boxSpace();
229 size.rheight() = columnTop + columnMaxHeight + mSettings.
boxSpace();
230 size.rwidth() = totalWidth;
231 if ( !mSettings.
title().isEmpty() )
233 size.rwidth() = std::max( titleSize.width(), size.width() );
237 if ( mLegendSize.isValid() )
239 qreal w = std::max( size.width(), mLegendSize.width() );
240 qreal h = std::max( size.height(), mLegendSize.height() );
241 size = QSizeF( w, h );
245 if ( !mSettings.
title().isEmpty() )
253 void QgsLegendRenderer::widthAndOffsetForTitleText(
const Qt::AlignmentFlag halignment,
const double legendWidth,
double &textBoxWidth,
double &textBoxLeft )
const
255 switch ( halignment )
259 textBoxWidth = legendWidth - 2 * mSettings.
boxSpace();
262 case Qt::AlignHCenter:
265 const double centerX = legendWidth / 2;
266 textBoxWidth = ( std::min(
static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.
boxSpace() ) * 2.0;
267 textBoxLeft = centerX - textBoxWidth / 2.;
273 QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList(
QgsLayerTreeGroup *parentGroup,
QgsRenderContext &context,
double indent )
275 QList<LegendComponentGroup> componentGroups;
278 return componentGroups;
280 const QList<QgsLayerTreeNode *> childNodes = parentGroup->
children();
286 QString style = node->customProperty( QStringLiteral(
"legend/title-style" ) ).toString();
288 double newIndent = indent;
289 if ( style == QLatin1String(
"subgroup" ) )
299 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
301 bool hasSubItems = !subgroups.empty();
305 LegendComponent component;
306 component.item = node;
307 component.indent = newIndent;
308 component.size = drawGroupTitle( nodeGroup, context );
310 if ( !subgroups.isEmpty() )
313 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
315 subgroups[0].components.prepend( component );
316 subgroups[0].size.rheight() += component.size.height();
317 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
318 if ( nodeGroup->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt() )
319 subgroups[0].placeColumnBreakBeforeGroup =
true;
324 LegendComponentGroup group;
325 group.placeColumnBreakBeforeGroup = nodeGroup->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt();
326 group.components.append( component );
327 group.size.rwidth() += component.size.width();
328 group.size.rheight() += component.size.height();
329 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
330 subgroups.append( group );
336 componentGroups.append( subgroups );
344 bool allowColumnSplit =
false;
351 allowColumnSplit =
true;
354 allowColumnSplit =
false;
358 LegendComponentGroup group;
359 group.placeColumnBreakBeforeGroup = nodeLayer->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt();
363 LegendComponent component;
364 component.item = node;
365 component.size = drawLayerTitle( nodeLayer, context );
366 component.indent = indent;
367 group.components.append( component );
368 group.size.rwidth() = component.size.width();
369 group.size.rheight() = component.size.height();
372 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->
layerLegendNodes( nodeLayer );
380 QList<LegendComponentGroup> layerGroups;
381 layerGroups.reserve( legendNodes.count() );
383 bool groupIsLayerGroup =
true;
385 for (
int j = 0; j < legendNodes.count(); j++ )
389 LegendComponent symbolComponent = drawSymbolItem(
legendNode, context, ColumnContext(), 0 );
393 if ( !allowColumnSplit || j == 0 )
397 if ( groupIsLayerGroup )
398 layerGroups.prepend( group );
400 layerGroups.append( group );
402 group = LegendComponentGroup();
403 group.placeColumnBreakBeforeGroup =
true;
404 groupIsLayerGroup =
false;
409 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
411 if ( !group.components.isEmpty() )
416 group.size.rheight() += symbolComponent.size.height();
417 symbolComponent.indent = indent;
418 group.components.append( symbolComponent );
422 if ( group.size.height() > 0 )
424 if ( groupIsLayerGroup )
425 layerGroups.prepend( group );
427 layerGroups.append( group );
428 group = LegendComponentGroup();
429 groupIsLayerGroup =
false;
431 LegendComponentGroup symbolGroup;
432 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
433 symbolComponent.indent = indent;
434 symbolGroup.components.append( symbolComponent );
435 symbolGroup.size.rwidth() = symbolComponent.size.width();
436 symbolGroup.size.rheight() = symbolComponent.size.height();
437 layerGroups.append( symbolGroup );
440 if ( group.size.height() > 0 )
442 if ( groupIsLayerGroup )
443 layerGroups.prepend( group );
445 layerGroups.append( group );
447 componentGroups.append( layerGroups );
451 return componentGroups;
455 int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
458 double totalHeight = 0;
459 qreal maxGroupHeight = 0;
460 int forcedColumnBreaks = 0;
461 double totalSpaceAboveGroups = 0;
462 for (
const LegendComponentGroup &group : std::as_const( componentGroups ) )
464 totalHeight += spaceAboveGroup( group );
465 totalSpaceAboveGroups += spaceAboveGroup( group );
466 totalHeight += group.size.height();
467 maxGroupHeight = std::max( group.size.height(), maxGroupHeight );
469 if ( group.placeColumnBreakBeforeGroup )
470 forcedColumnBreaks++;
472 double averageGroupHeight = ( totalHeight - totalSpaceAboveGroups ) / componentGroups.size();
474 if ( mSettings.
columnCount() == 0 && forcedColumnBreaks == 0 )
479 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.
columnCount() );
480 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
487 double maxColumnHeight = 0;
488 int currentColumn = 0;
489 int currentColumnGroupCount = 0;
490 double currentColumnHeight = 0;
491 int autoPlacedBreaks = 0;
494 double averageSpaceAboveGroups = 0;
495 if ( componentGroups.size() > targetNumberColumns )
496 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
499 totalHeight -= targetNumberColumns * averageSpaceAboveGroups;
501 for (
int i = 0; i < componentGroups.size(); i++ )
503 LegendComponentGroup group = componentGroups.at( i );
504 double currentHeight = currentColumnHeight;
505 if ( currentColumnGroupCount > 0 )
506 currentHeight += spaceAboveGroup( group );
507 currentHeight += group.size.height();
509 int numberRemainingGroups = componentGroups.size() - i;
512 int numberRemainingColumns = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
513 double avgColumnHeight = ( currentHeight + numberRemainingGroups * averageGroupHeight + ( numberRemainingGroups - numberRemainingColumns - 1 ) * averageSpaceAboveGroups ) / numberRemainingColumns;
516 int averageGroupsPerColumn = std::ceil( avgColumnHeight / ( averageGroupHeight + averageSpaceAboveGroups ) );
517 avgColumnHeight = averageGroupsPerColumn * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
519 bool canCreateNewColumn = ( currentColumnGroupCount > 0 )
520 && ( currentColumn < targetNumberColumns - 1 )
521 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
523 bool shouldCreateNewColumn = currentHeight > avgColumnHeight
524 && currentColumnGroupCount > 0
525 && currentHeight > maxGroupHeight
526 && currentHeight > maxColumnHeight;
528 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
529 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
533 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
535 if ( canCreateNewColumn && shouldCreateNewColumn )
539 if ( !group.placeColumnBreakBeforeGroup )
541 currentColumnGroupCount = 0;
542 currentColumnHeight = group.size.height();
546 currentColumnHeight = currentHeight;
548 componentGroups[i].column = currentColumn;
549 currentColumnGroupCount++;
550 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
554 QMap<QString, qreal> maxSymbolWidth;
555 for (
int i = 0; i < componentGroups.size(); i++ )
557 LegendComponentGroup &group = componentGroups[i];
558 for (
int j = 0; j < group.components.size(); j++ )
562 QString key = QStringLiteral(
"%1-%2" ).arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
563 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
567 for (
int i = 0; i < componentGroups.size(); i++ )
569 LegendComponentGroup &group = componentGroups[i];
570 for (
int j = 0; j < group.components.size(); j++ )
574 QString key = QStringLiteral(
"%1-%2" ).arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
577 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
578 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
579 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
583 return targetNumberColumns;
586 QSizeF QgsLegendRenderer::drawTitle(
QgsRenderContext &context,
double top, Qt::AlignmentFlag halignment,
double legendWidth )
589 if ( mSettings.
title().isEmpty() )
597 if (
auto *lPainter = context.
painter() )
599 lPainter->setPen( mSettings.
fontColor() );
605 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
609 for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
616 QRectF r( textBoxLeft, y, textBoxWidth, height );
620 mSettings.
drawText( context.
painter(), r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
624 size.rwidth() = std::max( width, size.rwidth() );
627 if ( titlePart != ( lines.end() - 1 ) )
632 size.rheight() = y - top;
638 double QgsLegendRenderer::spaceAboveGroup(
const LegendComponentGroup &group )
640 if ( group.components.isEmpty() )
return 0;
642 LegendComponent component = group.components.first();
644 if (
QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
648 else if (
QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
652 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
661 QSizeF QgsLegendRenderer::drawGroup(
const LegendComponentGroup &group,
QgsRenderContext &context, ColumnContext columnContext,
double top )
664 QSizeF size = QSizeF( group.size );
665 double currentY = top;
666 for (
const LegendComponent &component : std::as_const( group.components ) )
668 if (
QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
678 ColumnContext columnContextForItem = columnContext;
679 double indentWidth = component.indent;
692 columnContextForItem.left += indentWidth;
696 columnContextForItem.right -= indentWidth;
698 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
699 size.rwidth() = std::max( groupSize.width(), size.width() );
702 else if (
QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
713 ColumnContext columnContextForItem = columnContext;
714 double indentWidth = component.indent;
715 columnContextForItem.left += indentWidth;
716 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
717 size.rwidth() = std::max( subGroupSize.width(), size.width() );
727 ColumnContext columnContextForItem = columnContext;
728 double indentWidth = 0;
729 indentWidth = component.indent;
732 columnContextForItem.left += indentWidth;
736 columnContextForItem.right -= indentWidth;
739 LegendComponent symbolComponent = drawSymbolItem(
legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
741 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
743 currentY += component.size.height();
764 ctx.
point = QPointF( columnContext.left, top );
797 LegendComponent component;
798 component.item = symbolItem;
805 double width = std::max(
static_cast< double >( im.
symbolSize.width() ), maxSiblingSymbolWidth )
812 component.size = QSizeF( width, height );
819 QModelIndex idx = mLegendModel->
node2index( nodeLayer );
820 QString titleString = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
822 if ( titleString.isEmpty() )
827 if (
auto *lPainter = context.
painter() )
833 if ( nodeLayer->
layer() )
843 for ( QStringList::ConstIterator layerItemPart = lines.constBegin(); layerItemPart != lines.constEnd(); ++layerItemPart )
846 if ( QPainter *destPainter = context.
painter() )
848 double x = columnContext.left + sideMargin;
853 x = columnContext.right - labelWidth - sideMargin;
855 x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
857 mSettings.
drawText( destPainter, x, y, *layerItemPart, layerFont );
861 size.rwidth() = std::max( width, size.width() );
862 if ( layerItemPart != ( lines.end() - 1 ) )
868 size.rheight() = y - top;
880 QModelIndex idx = mLegendModel->
node2index( nodeGroup );
884 if (
auto *lPainter = context.
painter() )
885 lPainter->setPen( mSettings.
fontColor() );
892 for ( QStringList::ConstIterator groupPart = lines.constBegin(); groupPart != lines.constEnd(); ++groupPart )
896 if ( QPainter *destPainter = context.
painter() )
898 double x = columnContext.left + sideMargin;
903 x = columnContext.right - labelWidth - sideMargin;
905 x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
907 mSettings.
drawText( destPainter, x, y, *groupPart, groupFont );
910 size.rwidth() = std::max( width, size.width() );
911 if ( groupPart != ( lines.end() - 1 ) )
922 QString style = node->
customProperty( QStringLiteral(
"legend/title-style" ) ).toString();
923 if ( style == QLatin1String(
"hidden" ) )
925 else if ( style == QLatin1String(
"group" ) )
927 else if ( style == QLatin1String(
"subgroup" ) )
954 str = QStringLiteral(
"hidden" );
957 str = QStringLiteral(
"group" );
960 str = QStringLiteral(
"subgroup" );
966 if ( !
str.isEmpty() )
974 paintAndDetermineSize( context );