Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #2414 from vmora/varying_width_assistant
[FEATURE] assistant for varying line width
  • Loading branch information
vmora committed Nov 5, 2015
2 parents faeedfb + 6d1d213 commit 700fc04
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 63 deletions.
8 changes: 7 additions & 1 deletion python/core/qgsscaleexpression.sip
Expand Up @@ -36,8 +36,9 @@ class QgsScaleExpression : QgsExpression
* @param minSize minimum size
* @param maxSize maximum size
* @param nullSize size in case expression evaluates to NULL
* @param exponent to use in case of Exponential type
*/
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0 );
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0, double exponent = 1 );

operator bool() const;

Expand Down Expand Up @@ -74,6 +75,11 @@ class QgsScaleExpression : QgsExpression
*/
double nullSize() const;

/** Returns the exponent of the exponential expression.
* @see exponent
*/
double exponent() const;

/** Returns the base expression string (or field reference) used for
* calculating the values to be mapped to a size.
*/
Expand Down
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgslinesymbollayerv2.sip
Expand Up @@ -209,6 +209,8 @@ class QgsMarkerLineSymbolLayerV2 : QgsLineSymbolLayerV2

QSet<QString> usedAttributes() const;

void setDataDefinedProperty( const QString& property, QgsDataDefined* dataDefined /Transfer/ );

protected:

void renderPolylineInterval( const QPolygonF& points, QgsSymbolV2RenderContext& context );
Expand Down
47 changes: 31 additions & 16 deletions src/core/qgsscaleexpression.cpp
Expand Up @@ -26,21 +26,39 @@ QgsScaleExpression::QgsScaleExpression( const QString& expression )
, mMinValue( 0 )
, mMaxValue( 100 )
, mNullSize( 0 )
, mExponent( 1 )
{
init();
}

QgsScaleExpression::QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize )
: QgsExpression( createExpression( type, baseExpression, minValue, maxValue, minSize, maxSize, nullSize ) )
QgsScaleExpression::QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent )
: QgsExpression( createExpression( type, baseExpression, minValue, maxValue, minSize, maxSize, nullSize, exponent ) )
, mExpression( baseExpression )
, mType( type )
, mMinSize( minSize )
, mMaxSize( maxSize )
, mMinValue( minValue )
, mMaxValue( maxValue )
, mNullSize( nullSize )
, mExponent( 1 )
{

switch ( type )
{
case Linear:
mExponent = 1;
break;
case Area:
mExponent = .5;
break;
case Flannery:
mExponent = .57;
break;
case Exponential:
mExponent = exponent;
break;
case Unknown:
break;
}
}

void QgsScaleExpression::init()
Expand Down Expand Up @@ -76,17 +94,15 @@ void QgsScaleExpression::init()
}
else if ( "scale_exp" == Functions()[f->fnIndex()]->name() )
{
const double exp = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
mExponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
if ( ! ok )
return;
if ( qgsDoubleNear( exp, 0.57, 0.001 ) )
if ( qgsDoubleNear( mExponent, 0.57, 0.001 ) )
mType = Flannery;
else if ( qgsDoubleNear( exp, 0.5, 0.001 ) )
else if ( qgsDoubleNear( mExponent, 0.5, 0.001 ) )
mType = Area;
else
{
return;
}
mType = Exponential;
}
else
{
Expand All @@ -111,24 +127,24 @@ void QgsScaleExpression::init()
mExpression = args[0]->dump();
}

QString QgsScaleExpression::createExpression( Type type, const QString & baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize )
QString QgsScaleExpression::createExpression( Type type, const QString & baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent )
{
QString minValueString = QString::number( minValue );
QString maxValueString = QString::number( maxValue );
QString minSizeString = QString::number( minSize );
QString maxSizeString = QString::number( maxSize );
QString nullSizeString = QString::number( nullSize );
QString exponentString = QString::number( exponent );

switch ( type )
{
case Linear:
return QString( "coalesce(scale_linear(%1, %2, %3, %4, %5), %6)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );

case Area:
return QString( "coalesce(scale_exp(%1, %2, %3, %4, %5, 0.5), %6)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );

case Flannery:
return QString( "coalesce(scale_exp(%1, %2, %3, %4, %5, 0.57), %6)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );
case Exponential:
return QString( "coalesce(scale_exp(%1, %2, %3, %4, %5, %6), %7)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, exponentString, nullSizeString );

case Unknown:
break;
Expand All @@ -144,10 +160,9 @@ double QgsScaleExpression::size( double value ) const
return mMinSize + ( qBound( mMinValue, value, mMaxValue ) - mMinValue ) * ( mMaxSize - mMinSize ) / ( mMaxValue - mMinValue );

case Area:
return mMinSize + qPow( qBound( mMinValue, value, mMaxValue ) - mMinValue, .5 ) * ( mMaxSize - mMinSize ) / qPow( mMaxValue - mMinValue, .5 );

case Flannery:
return mMinSize + qPow( qBound( mMinValue, value, mMaxValue ) - mMinValue, .57 ) * ( mMaxSize - mMinSize ) / qPow( mMaxValue - mMinValue, .57 );
case Exponential:
return mMinSize + qPow( qBound( mMinValue, value, mMaxValue ) - mMinValue, mExponent ) * ( mMaxSize - mMinSize ) / qPow( mMaxValue - mMinValue, mExponent );

case Unknown:
break;
Expand Down
12 changes: 10 additions & 2 deletions src/core/qgsscaleexpression.h
Expand Up @@ -34,6 +34,7 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
Linear,
Area,
Flannery,
Exponential,
Unknown
};

Expand All @@ -52,8 +53,9 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
* @param minSize minimum size
* @param maxSize maximum size
* @param nullSize size in case expression evaluates to NULL
* @param exponent to use in case of Exponential type
*/
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0 );
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0, double exponent = 1 );

operator bool() const { return ! mExpression.isEmpty(); }

Expand Down Expand Up @@ -90,6 +92,11 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
*/
double nullSize() const { return mNullSize; }

/** Returns the exponent of the exponential expression.
* @see exponent
*/
double exponent() const { return mExponent; }

/** Returns the base expression string (or field reference) used for
* calculating the values to be mapped to a size.
*/
Expand All @@ -108,9 +115,10 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
double mMinValue;
double mMaxValue;
double mNullSize;
double mExponent;

void init();
static QString createExpression( Type type, const QString& baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize );
static QString createExpression( Type type, const QString& baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent );

};

Expand Down
9 changes: 9 additions & 0 deletions src/core/symbology-ng/qgslinesymbollayerv2.cpp
Expand Up @@ -1502,6 +1502,15 @@ void QgsMarkerLineSymbolLayerV2::setWidth( double width )
mMarker->setSize( width );
}

void QgsMarkerLineSymbolLayerV2::setDataDefinedProperty( const QString& property, QgsDataDefined* dataDefined )
{
if ( property == QgsSymbolLayerV2::EXPR_WIDTH && mMarker && dataDefined )
{
mMarker->setDataDefinedSize( *dataDefined );
}
QgsLineSymbolLayerV2::setDataDefinedProperty( property, dataDefined );
}

double QgsMarkerLineSymbolLayerV2::width() const
{
return mMarker->size();
Expand Down
3 changes: 3 additions & 0 deletions src/core/symbology-ng/qgslinesymbollayerv2.h
Expand Up @@ -259,6 +259,9 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2

QSet<QString> usedAttributes() const override;

void setDataDefinedProperty( const QString& property, QgsDataDefined* dataDefined ) override;


protected:

void renderPolylineInterval( const QPolygonF& points, QgsSymbolV2RenderContext& context );
Expand Down
109 changes: 85 additions & 24 deletions src/gui/symbology-ng/qgssizescalewidget.cpp
Expand Up @@ -48,33 +48,60 @@ class ItemDelegate : public QItemDelegate

};

// RAII class to block a QObject signal until destroyed
struct SignalBlocker
{
SignalBlocker( QObject * object )
: mObject( object )
{
mObject->blockSignals( true );
}
~SignalBlocker()
{
mObject->blockSignals( false );
}
private:
QObject * mObject;
};

void QgsSizeScaleWidget::setFromSymbol()
{
if ( !mSymbol )
{
return;
}

QgsDataDefined ddSize = mSymbol->dataDefinedSize();
QgsDataDefined ddSize;
if ( dynamic_cast< const QgsMarkerSymbolV2*>( mSymbol ) )
{
ddSize = static_cast< const QgsMarkerSymbolV2*>( mSymbol )->dataDefinedSize();
}
else if ( dynamic_cast< const QgsLineSymbolV2*>( mSymbol ) )
{
ddSize = dynamic_cast< const QgsLineSymbolV2*>( mSymbol )->dataDefinedWidth();
}

QgsScaleExpression expr( ddSize.expressionString() );
if ( expr )
{
for ( int i = 0; i < scaleMethodComboBox->count(); i++ )
{
if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
{
scaleMethodComboBox->setCurrentIndex( i );
( SignalBlocker( scaleMethodComboBox ), scaleMethodComboBox->setCurrentIndex( i ) );
break;
}
}

mExpressionWidget->setField( expr.baseExpression() );

minValueSpinBox->setValue( expr.minValue() );
maxValueSpinBox->setValue( expr.maxValue() );
minSizeSpinBox->setValue( expr.minSize() );
maxSizeSpinBox->setValue( expr.maxSize() );
nullSizeSpinBox->setValue( expr.nullSize() );
// the (,) is used to create the Blocker first, then call the setter
// the unamed SignalBlocker is destroyed at the end of the line (semicolumn)
( SignalBlocker( mExpressionWidget ), mExpressionWidget->setField( expr.baseExpression() ) );
( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( expr.minValue() ) );
( SignalBlocker( maxValueSpinBox ), maxValueSpinBox->setValue( expr.maxValue() ) );
( SignalBlocker( minSizeSpinBox ), minSizeSpinBox->setValue( expr.minSize() ) );
( SignalBlocker( maxSizeSpinBox ), maxSizeSpinBox->setValue( expr.maxSize() ) );
( SignalBlocker( nullSizeSpinBox ), nullSizeSpinBox->setValue( expr.nullSize() ) );
( SignalBlocker( exponentSpinBox ), exponentSpinBox->setValue( expr.exponent() ) );
}
updatePreview();
}
Expand Down Expand Up @@ -104,7 +131,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
return expContext;
}

QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsMarkerSymbolV2 * symbol )
QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsSymbolV2 * symbol )
: mSymbol( symbol )
// we just use the minimumValue and maximumValue from the layer, unfortunately they are
// non const, so we get the layer from the registry instead
Expand Down Expand Up @@ -145,9 +172,22 @@ QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsM
mExpressionWidget->setLayer( mLayer );
}

scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
{
scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
}
else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
{
scaleMethodComboBox->addItem( tr( "Exponential" ), int( QgsScaleExpression::Exponential ) );
scaleMethodComboBox->addItem( tr( "Linear" ), int( QgsScaleExpression::Linear ) );
}
else
{
Q_ASSERT( false );
}


minSizeSpinBox->setShowClearButton( false );
maxSizeSpinBox->setShowClearButton( false );
Expand All @@ -163,6 +203,7 @@ QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsM
connect( minValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( maxValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( nullSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( exponentSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
//potentially very expensive for large layers:
connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( computeFromLayerTriggered() ) );
connect( scaleMethodComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updatePreview() ) );
Expand All @@ -187,7 +228,8 @@ QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
maxValueSpinBox->value(),
minSizeSpinBox->value(),
maxSizeSpinBox->value(),
nullSizeSpinBox->value() );
nullSizeSpinBox->value(),
exponentSpinBox->value() );
}

void QgsSizeScaleWidget::updatePreview()
Expand All @@ -196,21 +238,40 @@ void QgsSizeScaleWidget::updatePreview()
return;

QScopedPointer<QgsScaleExpression> expr( createExpression() );

if ( expr->type() == QgsScaleExpression::Exponential )
exponentSpinBox->show();
else
exponentSpinBox->hide();

QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks( expr->minValue(), expr->maxValue(), 4 );

treeView->setIconSize( QSize( 512, 512 ) );
mPreviewList.clear();
int widthMax = 0;
for ( int i = 0; i < breaks.length(); i++ )
{
QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedSize( QgsDataDefined() );
symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
symbol->setSize( expr->size( breaks[i] ) );
QgsSymbolV2LegendNode node( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) );
const QSize sz( node.minimumIconSize() );
node.setIconSize( sz );
QScopedPointer< QStandardItem > item( new QStandardItem( node.data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
QScopedPointer< QgsSymbolV2LegendNode > node;
if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
{
QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedSize( QgsDataDefined() );
symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
symbol->setSize( expr->size( breaks[i] ) );
node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) ) );
}
else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
{
QScopedPointer< QgsLineSymbolV2 > symbol( static_cast<QgsLineSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedWidth( QgsDataDefined() );
symbol->setWidth( expr->size( breaks[i] ) );
node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) ) );

}

const QSize sz( node->minimumIconSize() );
node->setIconSize( sz );
QScopedPointer< QStandardItem > item( new QStandardItem( node->data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
widthMax = qMax( sz.width(), widthMax );
mPreviewList.appendRow( item.take() );
}
Expand Down Expand Up @@ -269,8 +330,8 @@ void QgsSizeScaleWidget::computeFromLayerTriggered()
min = qMin( min, value );
}
}
minValueSpinBox->setValue( min );
maxValueSpinBox->setValue( max );
( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( min ) );
( SignalBlocker( maxSizeSpinBox ), maxValueSpinBox->setValue( max ) );
updatePreview();
}

0 comments on commit 700fc04

Please sign in to comment.