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 : qgis::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 : qgis::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 )
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.;
275 QList<LegendComponentGroup> componentGroups;
278 return componentGroups;
280 const QList<QgsLayerTreeNode *> childNodes = parentGroup->
children();
288 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context );
289 bool hasSubItems = !subgroups.empty();
293 LegendComponent component;
294 component.item = node;
295 component.size = drawGroupTitle( nodeGroup, context );
297 if ( !subgroups.isEmpty() )
300 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
302 subgroups[0].components.prepend( component );
303 subgroups[0].size.rheight() += component.size.height();
304 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
305 if ( nodeGroup->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt() )
306 subgroups[0].placeColumnBreakBeforeGroup =
true;
311 LegendComponentGroup group;
312 group.placeColumnBreakBeforeGroup = nodeGroup->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt();
313 group.components.append( component );
314 group.size.rwidth() += component.size.width();
315 group.size.rheight() += component.size.height();
316 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
317 subgroups.append( group );
323 componentGroups.append( subgroups );
331 bool allowColumnSplit =
false;
338 allowColumnSplit =
true;
341 allowColumnSplit =
false;
345 LegendComponentGroup group;
346 group.placeColumnBreakBeforeGroup = nodeLayer->
customProperty( QStringLiteral(
"legend/column-break" ) ).toInt();
350 LegendComponent component;
351 component.item = node;
352 component.size = drawLayerTitle( nodeLayer, context );
353 group.components.append( component );
354 group.size.rwidth() = component.size.width();
355 group.size.rheight() = component.size.height();
358 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->
layerLegendNodes( nodeLayer );
366 QList<LegendComponentGroup> layerGroups;
367 layerGroups.reserve( legendNodes.count() );
369 bool groupIsLayerGroup =
true;
371 for (
int j = 0; j < legendNodes.count(); j++ )
375 LegendComponent symbolComponent = drawSymbolItem(
legendNode, context, ColumnContext(), 0 );
379 if ( !allowColumnSplit || j == 0 )
383 if ( groupIsLayerGroup )
384 layerGroups.prepend( group );
386 layerGroups.append( group );
388 group = LegendComponentGroup();
389 group.placeColumnBreakBeforeGroup =
true;
390 groupIsLayerGroup =
false;
395 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
397 if ( !group.components.isEmpty() )
402 group.size.rheight() += symbolComponent.size.height();
403 group.components.append( symbolComponent );
407 if ( group.size.height() > 0 )
409 if ( groupIsLayerGroup )
410 layerGroups.prepend( group );
412 layerGroups.append( group );
413 group = LegendComponentGroup();
414 groupIsLayerGroup =
false;
416 LegendComponentGroup symbolGroup;
417 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
418 symbolGroup.components.append( symbolComponent );
419 symbolGroup.size.rwidth() = symbolComponent.size.width();
420 symbolGroup.size.rheight() = symbolComponent.size.height();
421 layerGroups.append( symbolGroup );
424 if ( group.size.height() > 0 )
426 if ( groupIsLayerGroup )
427 layerGroups.prepend( group );
429 layerGroups.append( group );
431 componentGroups.append( layerGroups );
435 return componentGroups;
439 int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
442 double totalHeight = 0;
443 qreal maxGroupHeight = 0;
444 int forcedColumnBreaks = 0;
445 for (
const LegendComponentGroup &group : qgis::as_const( componentGroups ) )
447 totalHeight += spaceAboveGroup( group );
448 totalHeight += group.size.height();
449 maxGroupHeight = std::max( group.size.height(), maxGroupHeight );
451 if ( group.placeColumnBreakBeforeGroup )
452 forcedColumnBreaks++;
455 if ( mSettings.
columnCount() == 0 && forcedColumnBreaks == 0 )
460 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.
columnCount() );
461 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
468 double maxColumnHeight = 0;
469 int currentColumn = 0;
470 int currentColumnGroupCount = 0;
471 double currentColumnHeight = 0;
472 double closedColumnsHeight = 0;
473 int autoPlacedBreaks = 0;
475 for (
int i = 0; i < componentGroups.size(); i++ )
478 double avgColumnHeight = ( totalHeight - closedColumnsHeight ) / ( numberAutoPlacedBreaks + 1 - autoPlacedBreaks );
480 LegendComponentGroup group = componentGroups.at( i );
481 double currentHeight = currentColumnHeight;
482 if ( currentColumnGroupCount > 0 )
483 currentHeight += spaceAboveGroup( group );
484 currentHeight += group.size.height();
486 bool canCreateNewColumn = ( currentColumnGroupCount > 0 )
487 && ( currentColumn < targetNumberColumns - 1 )
488 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
490 bool shouldCreateNewColumn = ( currentHeight - avgColumnHeight ) > group.size.height() / 2
491 && currentColumnGroupCount > 0
492 && currentHeight > maxGroupHeight
493 && currentHeight > maxColumnHeight;
495 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
496 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
500 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
502 if ( canCreateNewColumn && shouldCreateNewColumn )
506 if ( !group.placeColumnBreakBeforeGroup )
508 currentColumnGroupCount = 0;
509 closedColumnsHeight += currentColumnHeight;
510 currentColumnHeight = group.size.height();
514 currentColumnHeight = currentHeight;
516 componentGroups[i].column = currentColumn;
517 currentColumnGroupCount++;
518 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
522 QMap<QString, qreal> maxSymbolWidth;
523 for (
int i = 0; i < componentGroups.size(); i++ )
525 LegendComponentGroup &group = componentGroups[i];
526 for (
int j = 0; j < group.components.size(); j++ )
530 QString key = QStringLiteral(
"%1-%2" ).arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
531 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
535 for (
int i = 0; i < componentGroups.size(); i++ )
537 LegendComponentGroup &group = componentGroups[i];
538 for (
int j = 0; j < group.components.size(); j++ )
542 QString key = QStringLiteral(
"%1-%2" ).arg(
reinterpret_cast< qulonglong
>(
legendNode->
layerNode() ) ).arg( group.column );
545 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
546 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
547 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
551 return targetNumberColumns;
554 QSizeF QgsLegendRenderer::drawTitle(
QgsRenderContext &context,
double top, Qt::AlignmentFlag halignment,
double legendWidth )
557 if ( mSettings.
title().isEmpty() )
573 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
577 for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
584 QRectF r( textBoxLeft, y, textBoxWidth, height );
588 mSettings.
drawText( context.
painter(), r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
592 size.rwidth() = std::max( width, size.rwidth() );
595 if ( titlePart != ( lines.end() - 1 ) )
600 size.rheight() = y - top;
606 double QgsLegendRenderer::spaceAboveGroup(
const LegendComponentGroup &group )
608 if ( group.components.isEmpty() )
return 0;
610 LegendComponent component = group.components.first();
612 if (
QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
616 else if (
QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
620 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
629 QSizeF QgsLegendRenderer::drawGroup(
const LegendComponentGroup &group,
QgsRenderContext &context, ColumnContext columnContext,
double top )
632 QSizeF size = QSizeF( group.size );
633 double currentY = top;
634 for (
const LegendComponent &component : qgis::as_const( group.components ) )
636 if (
QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
646 groupSize = drawGroupTitle( groupItem, context, columnContext, currentY );
647 size.rwidth() = std::max( groupSize.width(), size.width() );
650 else if (
QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
660 subGroupSize = drawLayerTitle( layerItem, context, columnContext, currentY );
661 size.rwidth() = std::max( subGroupSize.width(), size.width() );
671 LegendComponent symbolComponent = drawSymbolItem(
legendNode, context, columnContext, currentY, component.maxSiblingSymbolWidth );
673 size.rwidth() = std::max( symbolComponent.size.width(), size.width() );
675 currentY += component.size.height();
696 ctx.
point = QPointF( columnContext.left, top );
729 LegendComponent component;
730 component.item = symbolItem;
737 double width = std::max(
static_cast< double >( im.
symbolSize.width() ), maxSiblingSymbolWidth )
744 component.size = QSizeF( width, height );
751 QModelIndex idx = mLegendModel->
node2index( nodeLayer );
752 QString titleString = mLegendModel->
data( idx, Qt::DisplayRole ).toString();
754 if ( titleString.isEmpty() )
765 if ( nodeLayer->
layer() )
775 for ( QStringList::ConstIterator layerItemPart = lines.constBegin(); layerItemPart != lines.constEnd(); ++layerItemPart )
778 if ( QPainter *destPainter = context.
painter() )
780 double x = columnContext.left + sideMargin;
785 x = columnContext.right - labelWidth - sideMargin;
787 x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
789 mSettings.
drawText( destPainter, x, y, *layerItemPart, layerFont );
793 size.rwidth() = std::max( width, size.width() );
794 if ( layerItemPart != ( lines.end() - 1 ) )
800 size.rheight() = y - top;
812 QModelIndex idx = mLegendModel->
node2index( nodeGroup );
824 for ( QStringList::ConstIterator groupPart = lines.constBegin(); groupPart != lines.constEnd(); ++groupPart )
828 if ( QPainter *destPainter = context.
painter() )
830 double x = columnContext.left + sideMargin;
835 x = columnContext.right - labelWidth - sideMargin;
837 x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
839 mSettings.
drawText( destPainter, x, y, *groupPart, groupFont );
842 size.rwidth() = std::max( width, size.width() );
843 if ( groupPart != ( lines.end() - 1 ) )
854 QString style = node->
customProperty( QStringLiteral(
"legend/title-style" ) ).toString();
855 if ( style == QLatin1String(
"hidden" ) )
857 else if ( style == QLatin1String(
"group" ) )
859 else if ( style == QLatin1String(
"subgroup" ) )
886 str = QStringLiteral(
"hidden" );
889 str = QStringLiteral(
"group" );
892 str = QStringLiteral(
"subgroup" );
898 if ( !str.isEmpty() )
906 paintAndDetermineSize( context );