Skip to content

Commit

Permalink
Merge pull request #2035 from vmora/multivariate_legend
Browse files Browse the repository at this point in the history
Avoid symbol cropping in legend
  • Loading branch information
wonder-sk committed May 20, 2015
2 parents d978f3f + b8bc181 commit 3487471
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 22 deletions.
3 changes: 2 additions & 1 deletion python/core/effects/qgsimageoperation.sip
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ class QgsImageOperation
* @param minSize minimum size for cropped image, if desired. If the
* cropped image is smaller than the minimum size, it will be centered
* in the returned image.
* @param center croped image will be centered on the center of the original image
* @note added in QGIS 2.9
*/
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize() );
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize(), bool center = false );

};
10 changes: 10 additions & 0 deletions python/core/layertree/qgslayertreemodellegendnode.sip
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
virtual bool isScaleOK( double scale ) const;

virtual void invalidateMapBasedData();

//! Set the icon size
//! @note added in 2.10
void setIconSize( const QSize& sz );
//! @note added in 2.10
QSize iconSize() const;

//! Get the minimum icon size to prevent cropping
//! @note added in 2.10
QSize minimumIconSize() const;
};


Expand Down
12 changes: 11 additions & 1 deletion src/core/effects/qgsimageoperation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ void QgsImageOperation::flipImage( QImage &image, QgsImageOperation::FlipType ty
runLineOperation( image, flipOperation );
}

QImage QgsImageOperation::cropTransparent( const QImage &image, const QSize &minSize )
QImage QgsImageOperation::cropTransparent( const QImage &image, const QSize &minSize, bool center )
{
int width = image.width();
int height = image.height();
Expand Down Expand Up @@ -828,6 +828,16 @@ QImage QgsImageOperation::cropTransparent( const QImage &image, const QSize &min
ymax = ymin + minSize.height();
}
}
if ( center )
{
// recompute min and max to center image
const int dx = qMax( qAbs( xmax - width / 2 ), qAbs( xmin - width / 2 ) );
const int dy = qMax( qAbs( ymax - height / 2 ), qAbs( ymin - height / 2 ) );
xmin = qMax( 0, width / 2 - dx );
xmax = qMin( width, width / 2 + dx );
ymin = qMax( 0, height / 2 - dy );
ymax = qMin( height, height / 2 + dy );
}
return image.copy( xmin, ymin, xmax - xmin, ymax - ymin );
}

Expand Down
3 changes: 2 additions & 1 deletion src/core/effects/qgsimageoperation.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,10 @@ class CORE_EXPORT QgsImageOperation
* @param minSize minimum size for cropped image, if desired. If the
* cropped image is smaller than the minimum size, it will be centered
* in the returned image.
* @param center croped image will be centered on the center of the original image
* @note added in QGIS 2.9
*/
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize() );
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize(), bool center = false );

private:

Expand Down
82 changes: 64 additions & 18 deletions src/core/layertree/qgslayertreemodellegendnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "qgsrasterlayer.h"
#include "qgsrendererv2.h"
#include "qgssymbollayerv2utils.h"
#include "qgsimageoperation.h"
#include "qgsvectorlayer.h"


Expand Down Expand Up @@ -135,6 +136,7 @@ QgsSymbolV2LegendNode::QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, cons
: QgsLayerTreeModelLegendNode( nodeLayer, parent )
, mItem( item )
, mSymbolUsesMapUnits( false )
, mIconSize( 16, 16 )
{
updateLabel();

Expand All @@ -155,6 +157,64 @@ Qt::ItemFlags QgsSymbolV2LegendNode::flags() const
}


QSize QgsSymbolV2LegendNode::minimumIconSize() const
{
QSize minSz;
if ( mItem.symbol() && mItem.symbol()->type() == QgsSymbolV2::Marker )
{
QScopedPointer<QgsRenderContext> context( createTemporaryRenderContext() );
QPixmap pix = QPixmap::fromImage(
QgsImageOperation::cropTransparent(
QgsSymbolLayerV2Utils::symbolPreviewPixmap(
mItem.symbol(),
QSize( 512, 512 ),
context.data() ).toImage(),
mIconSize,
true ) );
minSz = pix.size();
}
else if ( mItem.symbol() && mItem.symbol()->type() == QgsSymbolV2::Line )
{
QScopedPointer<QgsRenderContext> context( createTemporaryRenderContext() );
QPixmap pix = QPixmap::fromImage(
QgsImageOperation::cropTransparent(
QgsSymbolLayerV2Utils::symbolPreviewPixmap(
mItem.symbol(),
QSize( mIconSize.width(), 512 ),
context.data() ).toImage(),
mIconSize,
true ) );
minSz = pix.size();
}
else
{
minSz = mIconSize;
}

if ( mItem.level() != 0 && !( model() && model()->testFlag( QgsLayerTreeModel::ShowLegendAsTree ) ) )
minSz.setWidth( indentSize + minSz.width() );

return minSz;
}

inline
QgsRenderContext * QgsSymbolV2LegendNode::createTemporaryRenderContext() const
{
double scale = 0.0;
double mupp = 0.0;
int dpi = 0;
if ( model() )
model()->legendMapViewData( &mupp, &dpi, &scale );
bool validData = mupp != 0 && dpi != 0 && scale != 0;

// setup temporary render context
QScopedPointer<QgsRenderContext> context( new QgsRenderContext );
context->setScaleFactor( dpi / 25.4 );
context->setRendererScale( scale );
context->setMapToPixel( QgsMapToPixel( mupp ) ); // hope it's ok to leave out other params
return validData ? context.take() : 0;
}

QVariant QgsSymbolV2LegendNode::data( int role ) const
{
if ( role == Qt::DisplayRole )
Expand All @@ -167,31 +227,17 @@ QVariant QgsSymbolV2LegendNode::data( int role ) const
}
else if ( role == Qt::DecorationRole )
{
QSize iconSize( 16, 16 ); // TODO: configurable
const int indentSize = 20;
if ( mPixmap.isNull() )
if ( mPixmap.isNull() || mPixmap.size() != mIconSize )
{
QPixmap pix;
if ( mItem.symbol() )
{
double scale = 0.0;
double mupp = 0.0;
int dpi = 0;
if ( model() )
model()->legendMapViewData( &mupp, &dpi, &scale );
bool validData = mupp != 0 && dpi != 0 && scale != 0;

// setup temporary render context
QgsRenderContext context;
context.setScaleFactor( dpi / 25.4 );
context.setRendererScale( scale );
context.setMapToPixel( QgsMapToPixel( mupp ) ); // hope it's ok to leave out other params

pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), iconSize, validData ? &context : 0 );
QScopedPointer<QgsRenderContext> context( createTemporaryRenderContext() );
pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), mIconSize, context.data() );
}
else
{
pix = QPixmap( iconSize );
pix = QPixmap( mIconSize );
pix.fill( Qt::transparent );
}

Expand Down
19 changes: 19 additions & 0 deletions src/core/layertree/qgslayertreemodellegendnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class QgsLayerTreeModel;
class QgsLegendSettings;
class QgsMapSettings;
class QgsSymbolV2;
class QgsRenderContext;

/**
* The QgsLegendRendererItem class is abstract interface for legend items
Expand Down Expand Up @@ -162,6 +163,16 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode

virtual void invalidateMapBasedData() override;

//! Set the icon size
//! @note added in 2.10
void setIconSize( const QSize& sz ) { mIconSize = sz; }
//! @note added in 2.10
QSize iconSize() const { return mIconSize; }

//! Get the minimum icon size to prevent cropping
//! @note added in 2.10
QSize minimumIconSize() const;

private:
void updateLabel();

Expand All @@ -170,6 +181,14 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
mutable QPixmap mPixmap; // cached symbol preview
QString mLabel;
bool mSymbolUsesMapUnits;
QSize mIconSize;

// ident the symbol icon to make it look like a tree structure
static const int indentSize = 20;

// return a temporary context or null if legendMapViewData are not valid
QgsRenderContext * createTemporaryRenderContext() const;

};


Expand Down
26 changes: 25 additions & 1 deletion src/core/qgsmaplayerlegend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,33 @@ QList<QgsLayerTreeModelLegendNode*> QgsDefaultVectorLayerLegend::createLayerTree
nodes.append( new QgsSimpleLegendNode( nodeLayer, r->legendClassificationAttribute() ) );
}

// we have varying icon sizes, and we want icon to be centered and
// text to be left aligned, so we have to compute the max width of icons
//
// we do that for nodes who share a common parent

QList<QgsSymbolV2LegendNode*> symbolNodes;
QMap<QString, int> widthMax;
foreach ( const QgsLegendSymbolItemV2& i, r->legendSymbolItemsV2() )
{
nodes.append( new QgsSymbolV2LegendNode( nodeLayer, i ) );
QgsSymbolV2LegendNode * n = new QgsSymbolV2LegendNode( nodeLayer, i );
nodes.append( n );
if ( i.symbol() )
{
const QSize sz( n->minimumIconSize() );
const QString parentKey( n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString() );
widthMax[parentKey] = qMax( sz.width(), widthMax.contains( parentKey ) ? widthMax[parentKey] : 0 );
n->setIconSize( sz );
symbolNodes.append( n );
}
}

foreach ( QgsSymbolV2LegendNode* n, symbolNodes )
{
const QString parentKey( n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString() );
Q_ASSERT( widthMax[parentKey] > 0 );
const int twiceMarginWidth = 2; // a one pixel margin avoids hugly rendering of icon
n->setIconSize( QSize( widthMax[parentKey] + twiceMarginWidth, n->iconSize().rheight() + twiceMarginWidth ) );
}

if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() )
Expand Down

0 comments on commit 3487471

Please sign in to comment.