mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-24 00:06:24 -04:00
Merge pull request #4169 from nyalldawson/props_gui
[FEATURE] Interactive curve editing for property overrides
This commit is contained in:
commit
bde4ff99c4
@ -22,13 +22,7 @@ class QgsHistogram
|
|||||||
*/
|
*/
|
||||||
void setValues( const QList<double>& values );
|
void setValues( const QList<double>& values );
|
||||||
|
|
||||||
/** Assigns numeric source values for the histogram from a vector layer's field or as the
|
bool setValues( const QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = 0 );
|
||||||
* result of an expression.
|
|
||||||
* @param layer vector layer
|
|
||||||
* @param fieldOrExpression field name or expression to be evaluated
|
|
||||||
* @returns true if values were successfully set
|
|
||||||
*/
|
|
||||||
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression );
|
|
||||||
|
|
||||||
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
|
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
|
||||||
* determined by the inter-quartile range of values and the number of values.
|
* determined by the inter-quartile range of values and the number of values.
|
||||||
|
@ -1,3 +1,35 @@
|
|||||||
|
class QgsCurveTransform
|
||||||
|
{
|
||||||
|
%TypeHeaderCode
|
||||||
|
#include <qgspropertytransformer.h>
|
||||||
|
%End
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
QgsCurveTransform();
|
||||||
|
QgsCurveTransform( const QList< QgsPoint >& controlPoints );
|
||||||
|
~QgsCurveTransform();
|
||||||
|
QgsCurveTransform( const QgsCurveTransform& other );
|
||||||
|
|
||||||
|
//QgsCurveTransform& operator=( const QgsCurveTransform& other );
|
||||||
|
|
||||||
|
QList< QgsPoint > controlPoints() const;
|
||||||
|
|
||||||
|
void setControlPoints( const QList< QgsPoint >& points );
|
||||||
|
|
||||||
|
void addControlPoint( double x, double y );
|
||||||
|
|
||||||
|
void removeControlPoint( double x, double y );
|
||||||
|
|
||||||
|
double y( double x ) const;
|
||||||
|
|
||||||
|
QVector< double > y( const QVector< double >& x ) const;
|
||||||
|
|
||||||
|
bool readXml( const QDomElement& elem, const QDomDocument& doc );
|
||||||
|
|
||||||
|
bool writeXml( QDomElement& transformElem, QDomDocument& doc ) const;
|
||||||
|
|
||||||
|
};
|
||||||
class QgsPropertyTransformer
|
class QgsPropertyTransformer
|
||||||
{
|
{
|
||||||
%TypeHeaderCode
|
%TypeHeaderCode
|
||||||
@ -28,6 +60,8 @@ class QgsPropertyTransformer
|
|||||||
|
|
||||||
QgsPropertyTransformer( double minValue = 0.0, double maxValue = 1.0 );
|
QgsPropertyTransformer( double minValue = 0.0, double maxValue = 1.0 );
|
||||||
|
|
||||||
|
QgsPropertyTransformer( const QgsPropertyTransformer& other );
|
||||||
|
|
||||||
virtual ~QgsPropertyTransformer();
|
virtual ~QgsPropertyTransformer();
|
||||||
|
|
||||||
virtual Type transformerType() const = 0;
|
virtual Type transformerType() const = 0;
|
||||||
@ -46,11 +80,18 @@ class QgsPropertyTransformer
|
|||||||
|
|
||||||
void setMaxValue( double max );
|
void setMaxValue( double max );
|
||||||
|
|
||||||
|
QgsCurveTransform* curveTransform() const;
|
||||||
|
|
||||||
|
void setCurveTransform( QgsCurveTransform* transform /Transfer/ );
|
||||||
|
|
||||||
virtual QVariant transform( const QgsExpressionContext& context, const QVariant& value ) const = 0;
|
virtual QVariant transform( const QgsExpressionContext& context, const QVariant& value ) const = 0;
|
||||||
virtual QString toExpression( const QString& baseExpression ) const = 0;
|
virtual QString toExpression( const QString& baseExpression ) const = 0;
|
||||||
|
|
||||||
static QgsPropertyTransformer* fromExpression( const QString& expression, QString& baseExpression /Out/, QString& fieldName /Out/ ) /Factory/;
|
static QgsPropertyTransformer* fromExpression( const QString& expression, QString& baseExpression /Out/, QString& fieldName /Out/ ) /Factory/;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
double transformNumeric( double input ) const;
|
||||||
};
|
};
|
||||||
class QgsGenericNumericTransformer : QgsPropertyTransformer
|
class QgsGenericNumericTransformer : QgsPropertyTransformer
|
||||||
{
|
{
|
||||||
@ -66,6 +107,8 @@ class QgsGenericNumericTransformer : QgsPropertyTransformer
|
|||||||
double nullOutput = 0.0,
|
double nullOutput = 0.0,
|
||||||
double exponent = 1.0 );
|
double exponent = 1.0 );
|
||||||
|
|
||||||
|
QgsGenericNumericTransformer( const QgsGenericNumericTransformer& other );
|
||||||
|
|
||||||
virtual Type transformerType() const;
|
virtual Type transformerType() const;
|
||||||
virtual QgsGenericNumericTransformer* clone() /Factory/;
|
virtual QgsGenericNumericTransformer* clone() /Factory/;
|
||||||
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const;
|
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const;
|
||||||
@ -108,6 +151,8 @@ class QgsSizeScaleTransformer : QgsPropertyTransformer
|
|||||||
double nullSize = 0.0,
|
double nullSize = 0.0,
|
||||||
double exponent = 1.0 );
|
double exponent = 1.0 );
|
||||||
|
|
||||||
|
QgsSizeScaleTransformer( const QgsSizeScaleTransformer& other );
|
||||||
|
|
||||||
virtual Type transformerType() const;
|
virtual Type transformerType() const;
|
||||||
virtual QgsSizeScaleTransformer* clone() /Factory/;
|
virtual QgsSizeScaleTransformer* clone() /Factory/;
|
||||||
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const;
|
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const;
|
||||||
|
@ -1352,27 +1352,9 @@ class QgsVectorLayer : QgsMapLayer, QgsExpressionContextGenerator
|
|||||||
QgsExpressionContext* context = nullptr,
|
QgsExpressionContext* context = nullptr,
|
||||||
bool* ok = nullptr ) const;
|
bool* ok = nullptr ) const;
|
||||||
|
|
||||||
/** Fetches all values from a specified field name or expression.
|
QList< QVariant > getValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false, QgsFeedback* feedback = 0 ) const;
|
||||||
* @param fieldOrExpression field name or an expression string
|
|
||||||
* @param ok will be set to false if field or expression is invalid, otherwise true
|
|
||||||
* @param selectedOnly set to true to get values from selected features only
|
|
||||||
* @returns list of fetched values
|
|
||||||
* @note added in QGIS 2.9
|
|
||||||
* @see getDoubleValues
|
|
||||||
*/
|
|
||||||
QList< QVariant > getValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false ) const;
|
|
||||||
|
|
||||||
/** Fetches all double values from a specified field name or expression. Null values or
|
QList< double > getDoubleValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false, int* nullCount = 0, QgsFeedback* feedback = 0 ) const;
|
||||||
* invalid expression results are skipped.
|
|
||||||
* @param fieldOrExpression field name or an expression string evaluating to a double value
|
|
||||||
* @param ok will be set to false if field or expression is invalid, otherwise true
|
|
||||||
* @param selectedOnly set to true to get values from selected features only
|
|
||||||
* @param nullCount optional pointer to integer to store number of null values encountered in
|
|
||||||
* @returns list of fetched values
|
|
||||||
* @note added in QGIS 2.9
|
|
||||||
* @see getValues
|
|
||||||
*/
|
|
||||||
QList< double > getDoubleValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false, int* nullCount = 0 ) const;
|
|
||||||
|
|
||||||
/** Set the blending mode used for rendering each feature */
|
/** Set the blending mode used for rendering each feature */
|
||||||
void setFeatureBlendMode( QPainter::CompositionMode blendMode );
|
void setFeatureBlendMode( QPainter::CompositionMode blendMode );
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
%Include qgsconfigureshortcutsdialog.sip
|
%Include qgsconfigureshortcutsdialog.sip
|
||||||
%Include qgscredentialdialog.sip
|
%Include qgscredentialdialog.sip
|
||||||
%Include qgscustomdrophandler.sip
|
%Include qgscustomdrophandler.sip
|
||||||
|
%Include qgscurveeditorwidget.sip
|
||||||
%Include qgsdetaileditemdata.sip
|
%Include qgsdetaileditemdata.sip
|
||||||
%Include qgsdetaileditemdelegate.sip
|
%Include qgsdetaileditemdelegate.sip
|
||||||
%Include qgsdetaileditemwidget.sip
|
%Include qgsdetaileditemwidget.sip
|
||||||
|
31
python/gui/qgscurveeditorwidget.sip
Normal file
31
python/gui/qgscurveeditorwidget.sip
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
class QgsCurveEditorWidget : QWidget
|
||||||
|
{
|
||||||
|
%TypeHeaderCode
|
||||||
|
#include <qgscurveeditorwidget.h>
|
||||||
|
%End
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
QgsCurveEditorWidget( QWidget* parent /TransferThis/ = 0, const QgsCurveTransform& curve = QgsCurveTransform() );
|
||||||
|
~QgsCurveEditorWidget();
|
||||||
|
|
||||||
|
QgsCurveTransform curve() const;
|
||||||
|
|
||||||
|
void setCurve( const QgsCurveTransform& curve );
|
||||||
|
void setHistogramSource( const QgsVectorLayer* layer, const QString& expression );
|
||||||
|
double minHistogramValueRange() const;
|
||||||
|
double maxHistogramValueRange() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setMinHistogramValueRange( double minValueRange );
|
||||||
|
void setMaxHistogramValueRange( double maxValueRange );
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
void changed();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void keyPressEvent( QKeyEvent *event );
|
||||||
|
|
||||||
|
};
|
@ -47,14 +47,14 @@ void QgsHistogram::setValues( const QList<double> &values )
|
|||||||
prepareValues();
|
prepareValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QgsHistogram::setValues( QgsVectorLayer *layer, const QString &fieldOrExpression )
|
bool QgsHistogram::setValues( const QgsVectorLayer *layer, const QString &fieldOrExpression, QgsFeedback* feedback )
|
||||||
{
|
{
|
||||||
mValues.clear();
|
mValues.clear();
|
||||||
if ( !layer )
|
if ( !layer )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
mValues = layer->getDoubleValues( fieldOrExpression, ok );
|
mValues = layer->getDoubleValues( fieldOrExpression, ok, false, nullptr, feedback );
|
||||||
if ( !ok )
|
if ( !ok )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
||||||
#include "qgis_core.h"
|
#include "qgis_core.h"
|
||||||
|
#include "qgsfeedback.h"
|
||||||
|
|
||||||
class QgsVectorLayer;
|
class QgsVectorLayer;
|
||||||
|
|
||||||
@ -49,9 +50,10 @@ class CORE_EXPORT QgsHistogram
|
|||||||
* result of an expression.
|
* result of an expression.
|
||||||
* @param layer vector layer
|
* @param layer vector layer
|
||||||
* @param fieldOrExpression field name or expression to be evaluated
|
* @param fieldOrExpression field name or expression to be evaluated
|
||||||
|
* @param feedback optional feedback object to allow cancelation of calculation
|
||||||
* @returns true if values were successfully set
|
* @returns true if values were successfully set
|
||||||
*/
|
*/
|
||||||
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression );
|
bool setValues( const QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = nullptr );
|
||||||
|
|
||||||
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
|
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
|
||||||
* determined by the inter-quartile range of values and the number of values.
|
* determined by the inter-quartile range of values and the number of values.
|
||||||
|
@ -50,11 +50,32 @@ QgsPropertyTransformer::QgsPropertyTransformer( double minValue, double maxValue
|
|||||||
, mMaxValue( maxValue )
|
, mMaxValue( maxValue )
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
QgsPropertyTransformer::QgsPropertyTransformer( const QgsPropertyTransformer& other )
|
||||||
|
: mMinValue( other.mMinValue )
|
||||||
|
, mMaxValue( other.mMaxValue )
|
||||||
|
, mCurveTransform( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr )
|
||||||
|
{}
|
||||||
|
|
||||||
|
QgsPropertyTransformer& QgsPropertyTransformer::operator=( const QgsPropertyTransformer & other )
|
||||||
|
{
|
||||||
|
mMinValue = other.mMinValue;
|
||||||
|
mMaxValue = other.mMaxValue;
|
||||||
|
mCurveTransform.reset( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
bool QgsPropertyTransformer::writeXml( QDomElement& transformerElem, QDomDocument& doc ) const
|
bool QgsPropertyTransformer::writeXml( QDomElement& transformerElem, QDomDocument& doc ) const
|
||||||
{
|
{
|
||||||
Q_UNUSED( doc );
|
Q_UNUSED( doc );
|
||||||
transformerElem.setAttribute( "minValue", QString::number( mMinValue ) );
|
transformerElem.setAttribute( "minValue", QString::number( mMinValue ) );
|
||||||
transformerElem.setAttribute( "maxValue", QString::number( mMaxValue ) );
|
transformerElem.setAttribute( "maxValue", QString::number( mMaxValue ) );
|
||||||
|
|
||||||
|
if ( mCurveTransform )
|
||||||
|
{
|
||||||
|
QDomElement curveElement = doc.createElement( "curve" );
|
||||||
|
mCurveTransform->writeXml( curveElement, doc );
|
||||||
|
transformerElem.appendChild( curveElement );
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +90,35 @@ QgsPropertyTransformer* QgsPropertyTransformer::fromExpression( const QString& e
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double QgsPropertyTransformer::transformNumeric( double input ) const
|
||||||
|
{
|
||||||
|
if ( !mCurveTransform )
|
||||||
|
return input;
|
||||||
|
|
||||||
|
if ( qgsDoubleNear( mMaxValue, mMinValue ) )
|
||||||
|
return input;
|
||||||
|
|
||||||
|
// convert input into target range
|
||||||
|
double scaledInput = ( input - mMinValue ) / ( mMaxValue - mMinValue );
|
||||||
|
|
||||||
|
return mMinValue + ( mMaxValue - mMinValue ) * mCurveTransform->y( scaledInput );
|
||||||
|
}
|
||||||
|
|
||||||
bool QgsPropertyTransformer::readXml( const QDomElement &transformerElem, const QDomDocument &doc )
|
bool QgsPropertyTransformer::readXml( const QDomElement &transformerElem, const QDomDocument &doc )
|
||||||
{
|
{
|
||||||
Q_UNUSED( doc );
|
Q_UNUSED( doc );
|
||||||
mMinValue = transformerElem.attribute( "minValue", "0.0" ).toDouble();
|
mMinValue = transformerElem.attribute( "minValue", "0.0" ).toDouble();
|
||||||
mMaxValue = transformerElem.attribute( "maxValue", "1.0" ).toDouble();
|
mMaxValue = transformerElem.attribute( "maxValue", "1.0" ).toDouble();
|
||||||
|
mCurveTransform.reset( nullptr );
|
||||||
|
|
||||||
|
QDomNodeList curveNodeList = transformerElem.elementsByTagName( "curve" );
|
||||||
|
if ( !curveNodeList.isEmpty() )
|
||||||
|
{
|
||||||
|
QDomElement curveElem = curveNodeList.at( 0 ).toElement();
|
||||||
|
mCurveTransform.reset( new QgsCurveTransform() );
|
||||||
|
mCurveTransform->readXml( curveElem, doc );
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,14 +134,35 @@ QgsGenericNumericTransformer::QgsGenericNumericTransformer( double minValue, dou
|
|||||||
, mExponent( exponent )
|
, mExponent( exponent )
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
QgsGenericNumericTransformer::QgsGenericNumericTransformer( const QgsGenericNumericTransformer& other )
|
||||||
|
: QgsPropertyTransformer( other )
|
||||||
|
, mMinOutput( other.mMinOutput )
|
||||||
|
, mMaxOutput( other.mMaxOutput )
|
||||||
|
, mNullOutput( other.mNullOutput )
|
||||||
|
, mExponent( other.mExponent )
|
||||||
|
{}
|
||||||
|
|
||||||
|
QgsGenericNumericTransformer& QgsGenericNumericTransformer::operator=( const QgsGenericNumericTransformer & other )
|
||||||
|
{
|
||||||
|
QgsPropertyTransformer::operator=( other );
|
||||||
|
mMinOutput = other.mMinOutput;
|
||||||
|
mMaxOutput = other.mMaxOutput;
|
||||||
|
mNullOutput = other.mNullOutput;
|
||||||
|
mExponent = other.mExponent;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
QgsGenericNumericTransformer *QgsGenericNumericTransformer::clone()
|
QgsGenericNumericTransformer *QgsGenericNumericTransformer::clone()
|
||||||
{
|
{
|
||||||
return new QgsGenericNumericTransformer( mMinValue,
|
std::unique_ptr< QgsGenericNumericTransformer > t( new QgsGenericNumericTransformer( mMinValue,
|
||||||
mMaxValue,
|
mMaxValue,
|
||||||
mMinOutput,
|
mMinOutput,
|
||||||
mMaxOutput,
|
mMaxOutput,
|
||||||
mNullOutput,
|
mNullOutput,
|
||||||
mExponent );
|
mExponent ) );
|
||||||
|
if ( mCurveTransform )
|
||||||
|
t->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
|
||||||
|
return t.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QgsGenericNumericTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
|
bool QgsGenericNumericTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
|
||||||
@ -126,6 +192,7 @@ bool QgsGenericNumericTransformer::readXml( const QDomElement &transformerElem,
|
|||||||
|
|
||||||
double QgsGenericNumericTransformer::value( double input ) const
|
double QgsGenericNumericTransformer::value( double input ) const
|
||||||
{
|
{
|
||||||
|
input = transformNumeric( input );
|
||||||
if ( qgsDoubleNear( mExponent, 1.0 ) )
|
if ( qgsDoubleNear( mExponent, 1.0 ) )
|
||||||
return mMinOutput + ( qBound( mMinValue, input, mMaxValue ) - mMinValue ) * ( mMaxOutput - mMinOutput ) / ( mMaxValue - mMinValue );
|
return mMinOutput + ( qBound( mMinValue, input, mMaxValue ) - mMinValue ) * ( mMaxOutput - mMinOutput ) / ( mMaxValue - mMinValue );
|
||||||
else
|
else
|
||||||
@ -257,15 +324,38 @@ QgsSizeScaleTransformer::QgsSizeScaleTransformer( ScaleType type, double minValu
|
|||||||
setType( type );
|
setType( type );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QgsSizeScaleTransformer::QgsSizeScaleTransformer( const QgsSizeScaleTransformer& other )
|
||||||
|
: QgsPropertyTransformer( other )
|
||||||
|
, mType( other.mType )
|
||||||
|
, mMinSize( other.mMinSize )
|
||||||
|
, mMaxSize( other.mMaxSize )
|
||||||
|
, mNullSize( other.mNullSize )
|
||||||
|
, mExponent( other.mExponent )
|
||||||
|
{}
|
||||||
|
|
||||||
|
QgsSizeScaleTransformer& QgsSizeScaleTransformer::operator=( const QgsSizeScaleTransformer & other )
|
||||||
|
{
|
||||||
|
QgsPropertyTransformer::operator=( other );
|
||||||
|
mType = other.mType;
|
||||||
|
mMinSize = other.mMinSize;
|
||||||
|
mMaxSize = other.mMaxSize;
|
||||||
|
mNullSize = other.mNullSize;
|
||||||
|
mExponent = other.mExponent;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
QgsSizeScaleTransformer *QgsSizeScaleTransformer::clone()
|
QgsSizeScaleTransformer *QgsSizeScaleTransformer::clone()
|
||||||
{
|
{
|
||||||
return new QgsSizeScaleTransformer( mType,
|
std::unique_ptr< QgsSizeScaleTransformer > t( new QgsSizeScaleTransformer( mType,
|
||||||
mMinValue,
|
mMinValue,
|
||||||
mMaxValue,
|
mMaxValue,
|
||||||
mMinSize,
|
mMinSize,
|
||||||
mMaxSize,
|
mMaxSize,
|
||||||
mNullSize,
|
mNullSize,
|
||||||
mExponent );
|
mExponent ) );
|
||||||
|
if ( mCurveTransform )
|
||||||
|
t->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
|
||||||
|
return t.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QgsSizeScaleTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
|
bool QgsSizeScaleTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
|
||||||
@ -297,6 +387,8 @@ bool QgsSizeScaleTransformer::readXml( const QDomElement &transformerElem, const
|
|||||||
|
|
||||||
double QgsSizeScaleTransformer::size( double value ) const
|
double QgsSizeScaleTransformer::size( double value ) const
|
||||||
{
|
{
|
||||||
|
value = transformNumeric( value );
|
||||||
|
|
||||||
switch ( mType )
|
switch ( mType )
|
||||||
{
|
{
|
||||||
case Linear:
|
case Linear:
|
||||||
@ -483,6 +575,7 @@ QgsColorRampTransformer::QgsColorRampTransformer( const QgsColorRampTransformer
|
|||||||
|
|
||||||
QgsColorRampTransformer &QgsColorRampTransformer::operator=( const QgsColorRampTransformer & other )
|
QgsColorRampTransformer &QgsColorRampTransformer::operator=( const QgsColorRampTransformer & other )
|
||||||
{
|
{
|
||||||
|
QgsPropertyTransformer::operator=( other );
|
||||||
mMinValue = other.mMinValue;
|
mMinValue = other.mMinValue;
|
||||||
mMaxValue = other.mMaxValue;
|
mMaxValue = other.mMaxValue;
|
||||||
mGradientRamp.reset( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr );
|
mGradientRamp.reset( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr );
|
||||||
@ -493,11 +586,13 @@ QgsColorRampTransformer &QgsColorRampTransformer::operator=( const QgsColorRampT
|
|||||||
|
|
||||||
QgsColorRampTransformer* QgsColorRampTransformer::clone()
|
QgsColorRampTransformer* QgsColorRampTransformer::clone()
|
||||||
{
|
{
|
||||||
QgsColorRampTransformer* c = new QgsColorRampTransformer( mMinValue, mMaxValue,
|
std::unique_ptr< QgsColorRampTransformer > c( new QgsColorRampTransformer( mMinValue, mMaxValue,
|
||||||
mGradientRamp ? mGradientRamp->clone() : nullptr,
|
mGradientRamp ? mGradientRamp->clone() : nullptr,
|
||||||
mNullColor );
|
mNullColor ) );
|
||||||
c->setRampName( mRampName );
|
c->setRampName( mRampName );
|
||||||
return c;
|
if ( mCurveTransform )
|
||||||
|
c->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
|
||||||
|
return c.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QgsColorRampTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
|
bool QgsColorRampTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
|
||||||
@ -569,6 +664,7 @@ QString QgsColorRampTransformer::toExpression( const QString& baseExpression ) c
|
|||||||
|
|
||||||
QColor QgsColorRampTransformer::color( double value ) const
|
QColor QgsColorRampTransformer::color( double value ) const
|
||||||
{
|
{
|
||||||
|
value = transformNumeric( value );
|
||||||
double scaledVal = qBound( 0.0, ( value - mMinValue ) / ( mMaxValue - mMinValue ), 1.0 );
|
double scaledVal = qBound( 0.0, ( value - mMinValue ) / ( mMaxValue - mMinValue ), 1.0 );
|
||||||
|
|
||||||
if ( !mGradientRamp )
|
if ( !mGradientRamp )
|
||||||
@ -586,3 +682,340 @@ void QgsColorRampTransformer::setColorRamp( QgsColorRamp* ramp )
|
|||||||
{
|
{
|
||||||
mGradientRamp.reset( ramp );
|
mGradientRamp.reset( ramp );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// QgsCurveTransform
|
||||||
|
//
|
||||||
|
|
||||||
|
bool sortByX( const QgsPoint& a, const QgsPoint& b )
|
||||||
|
{
|
||||||
|
return a.x() < b.x();
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsCurveTransform::QgsCurveTransform()
|
||||||
|
{
|
||||||
|
mControlPoints << QgsPoint( 0, 0 ) << QgsPoint( 1, 1 );
|
||||||
|
calcSecondDerivativeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsCurveTransform::QgsCurveTransform( const QList<QgsPoint>& controlPoints )
|
||||||
|
: mControlPoints( controlPoints )
|
||||||
|
{
|
||||||
|
std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
|
||||||
|
calcSecondDerivativeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsCurveTransform::~QgsCurveTransform()
|
||||||
|
{
|
||||||
|
delete [] mSecondDerivativeArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsCurveTransform::QgsCurveTransform( const QgsCurveTransform& other )
|
||||||
|
: mControlPoints( other.mControlPoints )
|
||||||
|
{
|
||||||
|
if ( other.mSecondDerivativeArray )
|
||||||
|
{
|
||||||
|
mSecondDerivativeArray = new double[ mControlPoints.count()];
|
||||||
|
memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsCurveTransform& QgsCurveTransform::operator=( const QgsCurveTransform & other )
|
||||||
|
{
|
||||||
|
mControlPoints = other.mControlPoints;
|
||||||
|
if ( other.mSecondDerivativeArray )
|
||||||
|
{
|
||||||
|
delete [] mSecondDerivativeArray;
|
||||||
|
mSecondDerivativeArray = new double[ mControlPoints.count()];
|
||||||
|
memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveTransform::setControlPoints( const QList<QgsPoint>& points )
|
||||||
|
{
|
||||||
|
mControlPoints = points;
|
||||||
|
std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
|
||||||
|
for ( int i = 0; i < mControlPoints.count(); ++i )
|
||||||
|
{
|
||||||
|
mControlPoints[ i ] = QgsPoint( qBound( 0.0, mControlPoints.at( i ).x(), 1.0 ),
|
||||||
|
qBound( 0.0, mControlPoints.at( i ).y(), 1.0 ) );
|
||||||
|
}
|
||||||
|
calcSecondDerivativeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveTransform::addControlPoint( double x, double y )
|
||||||
|
{
|
||||||
|
QgsPoint point( x, y );
|
||||||
|
if ( mControlPoints.contains( point ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
mControlPoints << point;
|
||||||
|
std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
|
||||||
|
calcSecondDerivativeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveTransform::removeControlPoint( double x, double y )
|
||||||
|
{
|
||||||
|
for ( int i = 0; i < mControlPoints.count(); ++i )
|
||||||
|
{
|
||||||
|
if ( qgsDoubleNear( mControlPoints.at( i ).x(), x )
|
||||||
|
&& qgsDoubleNear( mControlPoints.at( i ).y(), y ) )
|
||||||
|
{
|
||||||
|
mControlPoints.removeAt( i );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calcSecondDerivativeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
|
||||||
|
// which in turn was adapted from
|
||||||
|
// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
|
||||||
|
|
||||||
|
double QgsCurveTransform::y( double x ) const
|
||||||
|
{
|
||||||
|
int n = mControlPoints.count();
|
||||||
|
if ( n < 2 )
|
||||||
|
return qBound( 0.0, x, 1.0 ); // invalid
|
||||||
|
else if ( n < 3 )
|
||||||
|
{
|
||||||
|
// linear
|
||||||
|
if ( x <= mControlPoints.at( 0 ).x() )
|
||||||
|
return qBound( 0.0, mControlPoints.at( 0 ).y(), 1.0 );
|
||||||
|
else if ( x >= mControlPoints.at( n - 1 ).x() )
|
||||||
|
return qBound( 0.0, mControlPoints.at( 1 ).y(), 1.0 );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double dx = mControlPoints.at( 1 ).x() - mControlPoints.at( 0 ).x();
|
||||||
|
double dy = mControlPoints.at( 1 ).y() - mControlPoints.at( 0 ).y();
|
||||||
|
return qBound( 0.0, ( x - mControlPoints.at( 0 ).x() ) * ( dy / dx ) + mControlPoints.at( 0 ).y(), 1.0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safety check
|
||||||
|
if ( x <= mControlPoints.at( 0 ).x() )
|
||||||
|
return qBound( 0.0, mControlPoints.at( 0 ).y(), 1.0 );
|
||||||
|
if ( x >= mControlPoints.at( n - 1 ).x() )
|
||||||
|
return qBound( 0.0, mControlPoints.at( n - 1 ).y(), 1.0 );
|
||||||
|
|
||||||
|
// find corresponding segment
|
||||||
|
QList<QgsPoint>::const_iterator pointIt = mControlPoints.constBegin();
|
||||||
|
QgsPoint currentControlPoint = *pointIt;
|
||||||
|
++pointIt;
|
||||||
|
QgsPoint nextControlPoint = *pointIt;
|
||||||
|
|
||||||
|
for ( int i = 0; i < n - 1; ++i )
|
||||||
|
{
|
||||||
|
if ( x < nextControlPoint.x() )
|
||||||
|
{
|
||||||
|
// found segment
|
||||||
|
double h = nextControlPoint.x() - currentControlPoint.x();
|
||||||
|
double t = ( x - currentControlPoint.x() ) / h;
|
||||||
|
|
||||||
|
double a = 1 - t;
|
||||||
|
|
||||||
|
return qBound( 0.0, a*currentControlPoint.y() + t*nextControlPoint.y() + ( h*h / 6 )*(( a*a*a - a )*mSecondDerivativeArray[i] + ( t*t*t - t )*mSecondDerivativeArray[i+1] ),
|
||||||
|
1.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
++pointIt;
|
||||||
|
if ( pointIt == mControlPoints.constEnd() )
|
||||||
|
break;
|
||||||
|
|
||||||
|
currentControlPoint = nextControlPoint;
|
||||||
|
nextControlPoint = *pointIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
//should not happen
|
||||||
|
return qBound( 0.0, x, 1.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
|
||||||
|
// which in turn was adapted from
|
||||||
|
// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
|
||||||
|
|
||||||
|
QVector<double> QgsCurveTransform::y( const QVector<double>& x ) const
|
||||||
|
{
|
||||||
|
QVector<double> result;
|
||||||
|
|
||||||
|
int n = mControlPoints.count();
|
||||||
|
if ( n < 3 )
|
||||||
|
{
|
||||||
|
// invalid control points - use simple transform
|
||||||
|
Q_FOREACH ( double i, x )
|
||||||
|
result << y( i );
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find corresponding segment
|
||||||
|
QList<QgsPoint>::const_iterator pointIt = mControlPoints.constBegin();
|
||||||
|
QgsPoint currentControlPoint = *pointIt;
|
||||||
|
++pointIt;
|
||||||
|
QgsPoint nextControlPoint = *pointIt;
|
||||||
|
|
||||||
|
int xIndex = 0;
|
||||||
|
double currentX = x.at( xIndex );
|
||||||
|
// safety check
|
||||||
|
while ( currentX <= currentControlPoint.x() )
|
||||||
|
{
|
||||||
|
result << qBound( 0.0, currentControlPoint.y(), 1.0 );
|
||||||
|
xIndex++;
|
||||||
|
currentX = x.at( xIndex );
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 0; i < n - 1; ++i )
|
||||||
|
{
|
||||||
|
while ( currentX < nextControlPoint.x() )
|
||||||
|
{
|
||||||
|
// found segment
|
||||||
|
double h = nextControlPoint.x() - currentControlPoint.x();
|
||||||
|
|
||||||
|
double t = ( currentX - currentControlPoint.x() ) / h;
|
||||||
|
|
||||||
|
double a = 1 - t;
|
||||||
|
|
||||||
|
result << qBound( 0.0, a*currentControlPoint.y() + t*nextControlPoint.y() + ( h*h / 6 )*(( a*a*a - a )*mSecondDerivativeArray[i] + ( t*t*t - t )*mSecondDerivativeArray[i+1] ), 1.0 );
|
||||||
|
xIndex++;
|
||||||
|
if ( xIndex == x.count() )
|
||||||
|
return result;
|
||||||
|
|
||||||
|
currentX = x.at( xIndex );
|
||||||
|
}
|
||||||
|
|
||||||
|
++pointIt;
|
||||||
|
if ( pointIt == mControlPoints.constEnd() )
|
||||||
|
break;
|
||||||
|
|
||||||
|
currentControlPoint = nextControlPoint;
|
||||||
|
nextControlPoint = *pointIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// safety check
|
||||||
|
while ( xIndex < x.count() )
|
||||||
|
{
|
||||||
|
result << qBound( 0.0, nextControlPoint.y(), 1.0 );
|
||||||
|
xIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsCurveTransform::readXml( const QDomElement& elem, const QDomDocument& )
|
||||||
|
{
|
||||||
|
QString xString = elem.attribute( QStringLiteral( "x" ) );
|
||||||
|
QString yString = elem.attribute( QStringLiteral( "y" ) );
|
||||||
|
|
||||||
|
QStringList xVals = xString.split( ',' );
|
||||||
|
QStringList yVals = yString.split( ',' );
|
||||||
|
if ( xVals.count() != yVals.count() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QList< QgsPoint > newPoints;
|
||||||
|
bool ok = false;
|
||||||
|
for ( int i = 0; i < xVals.count(); ++i )
|
||||||
|
{
|
||||||
|
double x = xVals.at( i ).toDouble( &ok );
|
||||||
|
if ( !ok )
|
||||||
|
return false;
|
||||||
|
double y = yVals.at( i ).toDouble( &ok );
|
||||||
|
if ( !ok )
|
||||||
|
return false;
|
||||||
|
newPoints << QgsPoint( x, y );
|
||||||
|
}
|
||||||
|
setControlPoints( newPoints );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsCurveTransform::writeXml( QDomElement& transformElem, QDomDocument& ) const
|
||||||
|
{
|
||||||
|
QStringList x;
|
||||||
|
QStringList y;
|
||||||
|
Q_FOREACH ( const QgsPoint& p, mControlPoints )
|
||||||
|
{
|
||||||
|
x << qgsDoubleToString( p.x() );
|
||||||
|
y << qgsDoubleToString( p.y() );
|
||||||
|
}
|
||||||
|
|
||||||
|
transformElem.setAttribute( QStringLiteral( "x" ), x.join( ',' ) );
|
||||||
|
transformElem.setAttribute( QStringLiteral( "y" ), y.join( ',' ) );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
|
||||||
|
// which in turn was adapted from
|
||||||
|
// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
|
||||||
|
|
||||||
|
void QgsCurveTransform::calcSecondDerivativeArray()
|
||||||
|
{
|
||||||
|
int n = mControlPoints.count();
|
||||||
|
if ( n < 3 )
|
||||||
|
return; // cannot proceed
|
||||||
|
|
||||||
|
delete[] mSecondDerivativeArray;
|
||||||
|
|
||||||
|
double* matrix = new double[ n * 3 ];
|
||||||
|
double* result = new double[ n ];
|
||||||
|
matrix[0] = 0;
|
||||||
|
matrix[1] = 1;
|
||||||
|
matrix[2] = 0;
|
||||||
|
result[0] = 0;
|
||||||
|
QList<QgsPoint>::const_iterator pointIt = mControlPoints.constBegin();
|
||||||
|
QgsPoint pointIm1 = *pointIt;
|
||||||
|
++pointIt;
|
||||||
|
QgsPoint pointI = *pointIt;
|
||||||
|
++pointIt;
|
||||||
|
QgsPoint pointIp1 = *pointIt;
|
||||||
|
|
||||||
|
for ( int i = 1; i < n - 1; ++i )
|
||||||
|
{
|
||||||
|
matrix[i * 3 + 0 ] = ( pointI.x() - pointIm1.x() ) / 6.0;
|
||||||
|
matrix[i * 3 + 1 ] = ( pointIp1.x() - pointIm1.x() ) / 3.0;
|
||||||
|
matrix[i * 3 + 2 ] = ( pointIp1.x() - pointI.x() ) / 6.0;
|
||||||
|
result[i] = ( pointIp1.y() - pointI.y() ) / ( pointIp1.x() - pointI.x() ) - ( pointI.y() - pointIm1.y() ) / ( pointI.x() - pointIm1.x() );
|
||||||
|
|
||||||
|
// shuffle points
|
||||||
|
pointIm1 = pointI;
|
||||||
|
pointI = pointIp1;
|
||||||
|
++pointIt;
|
||||||
|
if ( pointIt == mControlPoints.constEnd() )
|
||||||
|
break;
|
||||||
|
|
||||||
|
pointIp1 = *pointIt;
|
||||||
|
}
|
||||||
|
matrix[( n-1 )*3 + 0] = 0;
|
||||||
|
matrix[( n-1 )*3 + 1] = 1;
|
||||||
|
matrix[( n-1 ) * 3 +2] = 0;
|
||||||
|
result[n-1] = 0;
|
||||||
|
|
||||||
|
// solving pass1 (up->down)
|
||||||
|
for ( int i = 1; i < n; ++i )
|
||||||
|
{
|
||||||
|
double k = matrix[i * 3 + 0] / matrix[( i-1 ) * 3 + 1];
|
||||||
|
matrix[i * 3 + 1] -= k * matrix[( i-1 )*3+2];
|
||||||
|
matrix[i * 3 + 0] = 0;
|
||||||
|
result[i] -= k * result[i-1];
|
||||||
|
}
|
||||||
|
// solving pass2 (down->up)
|
||||||
|
for ( int i = n - 2; i >= 0; --i )
|
||||||
|
{
|
||||||
|
double k = matrix[i*3+2] / matrix[( i+1 )*3+1];
|
||||||
|
matrix[i*3+1] -= k * matrix[( i+1 )*3+0];
|
||||||
|
matrix[i*3+2] = 0;
|
||||||
|
result[i] -= k * result[i+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// return second derivative value for each point
|
||||||
|
mSecondDerivativeArray = new double[n];
|
||||||
|
for ( int i = 0;i < n;++i )
|
||||||
|
{
|
||||||
|
mSecondDerivativeArray[i] = result[i] / matrix[( i*3 )+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] result;
|
||||||
|
delete[] matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "qgis_core.h"
|
#include "qgis_core.h"
|
||||||
#include "qgsexpression.h"
|
#include "qgsexpression.h"
|
||||||
#include "qgsexpressioncontext.h"
|
#include "qgsexpressioncontext.h"
|
||||||
|
#include "qgspoint.h"
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@ -26,9 +27,123 @@
|
|||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
class QgsColorRamp;
|
class QgsColorRamp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup core
|
||||||
|
* \class QgsCurveTransform
|
||||||
|
* \brief Handles scaling of input values to output values by using a curve created
|
||||||
|
* from smoothly joining a number of set control points.
|
||||||
|
*
|
||||||
|
* QgsCurveTransform assists in creation of curve type transforms, typically seen in
|
||||||
|
* raster image editing software (eg the curves dialog in GIMP or Photoshop).
|
||||||
|
* Transforms are created by passing a number of set control points through which
|
||||||
|
* the transform curve must pass. The curve is guaranteed to exactly pass through
|
||||||
|
* these control points. Between control points the curve is smoothly interpolated
|
||||||
|
* so that no disjoint sections or "corners" are present.
|
||||||
|
*
|
||||||
|
* If the first or last control point are not located at x = 0 and x = 1 respectively,
|
||||||
|
* then values outside this range will be mapped to the y value of either the first
|
||||||
|
* or last control point. In other words, the curve will have a flat segment
|
||||||
|
* for values outside of the control point range.
|
||||||
|
*
|
||||||
|
* \note Added in version 3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CORE_EXPORT QgsCurveTransform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a default QgsCurveTransform which linearly maps values
|
||||||
|
* between 0 and 1 unchanged. I.e. y == x.
|
||||||
|
*/
|
||||||
|
QgsCurveTransform();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a QgsCurveTransform using a specified list of \a controlPoints.
|
||||||
|
* Behavior is undefined if duplicate x values exist in the control points
|
||||||
|
* list.
|
||||||
|
*/
|
||||||
|
QgsCurveTransform( const QList< QgsPoint >& controlPoints );
|
||||||
|
|
||||||
|
~QgsCurveTransform();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor
|
||||||
|
*/
|
||||||
|
QgsCurveTransform( const QgsCurveTransform& other );
|
||||||
|
|
||||||
|
QgsCurveTransform& operator=( const QgsCurveTransform& other );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of the control points for the transform.
|
||||||
|
* @see setControlPoints()
|
||||||
|
*/
|
||||||
|
QList< QgsPoint > controlPoints() const { return mControlPoints; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of control points for the transform. Any existing
|
||||||
|
* points are removed.
|
||||||
|
* @see controlPoints()
|
||||||
|
*/
|
||||||
|
void setControlPoints( const QList< QgsPoint >& points );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a control point to the transform. Behavior is undefined if duplicate
|
||||||
|
* x values exist in the control points list.
|
||||||
|
* @see removeControlPoint()
|
||||||
|
*/
|
||||||
|
void addControlPoint( double x, double y );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a control point from the transform. This will have no effect if a
|
||||||
|
* matching control point does not exist.
|
||||||
|
* @see addControlPoint()
|
||||||
|
*/
|
||||||
|
void removeControlPoint( double x, double y );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mapped y value corresponding to the specified \a x value.
|
||||||
|
*/
|
||||||
|
double y( double x ) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of y values corresponding to a list of \a x values.
|
||||||
|
* Calling this method is faster then calling the double variant multiple
|
||||||
|
* times.
|
||||||
|
*/
|
||||||
|
QVector< double > y( const QVector< double >& x ) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the curve's state from an XML element.
|
||||||
|
* @param elem source DOM element for transform's state
|
||||||
|
* @param doc DOM document
|
||||||
|
* @see writeXml()
|
||||||
|
*/
|
||||||
|
bool readXml( const QDomElement& elem, const QDomDocument& doc );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the current state of the transform into an XML element
|
||||||
|
* @param transformElem destination element for the transform's state
|
||||||
|
* @param doc DOM document
|
||||||
|
* @see readXml()
|
||||||
|
*/
|
||||||
|
bool writeXml( QDomElement& transformElem, QDomDocument& doc ) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void calcSecondDerivativeArray();
|
||||||
|
|
||||||
|
QList< QgsPoint > mControlPoints;
|
||||||
|
|
||||||
|
double* mSecondDerivativeArray = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
* \class QgsPropertyTransformer
|
* \class QgsPropertyTransformer
|
||||||
@ -62,6 +177,12 @@ class CORE_EXPORT QgsPropertyTransformer
|
|||||||
*/
|
*/
|
||||||
QgsPropertyTransformer( double minValue = 0.0, double maxValue = 1.0 );
|
QgsPropertyTransformer( double minValue = 0.0, double maxValue = 1.0 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*/
|
||||||
|
QgsPropertyTransformer( const QgsPropertyTransformer& other );
|
||||||
|
QgsPropertyTransformer& operator=( const QgsPropertyTransformer& other );
|
||||||
|
|
||||||
virtual ~QgsPropertyTransformer() = default;
|
virtual ~QgsPropertyTransformer() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,6 +241,21 @@ class CORE_EXPORT QgsPropertyTransformer
|
|||||||
*/
|
*/
|
||||||
void setMaxValue( double max ) { mMaxValue = max; }
|
void setMaxValue( double max ) { mMaxValue = max; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the curve transform applied to input values before they are transformed
|
||||||
|
* by the individual transform subclasses.
|
||||||
|
* @see setCurveTransform()
|
||||||
|
*/
|
||||||
|
QgsCurveTransform* curveTransform() const { return mCurveTransform.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a curve transform to apply to input values before they are transformed
|
||||||
|
* by the individual transform subclasses. Ownership of \a transform is transferred
|
||||||
|
* to the property transformer.
|
||||||
|
* @see curveTransform()
|
||||||
|
*/
|
||||||
|
void setCurveTransform( QgsCurveTransform* transform ) { mCurveTransform.reset( transform ); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the transform of a value. Derived classes must implement this to perform their transformations
|
* Calculates the transform of a value. Derived classes must implement this to perform their transformations
|
||||||
* on input values
|
* on input values
|
||||||
@ -156,6 +292,15 @@ class CORE_EXPORT QgsPropertyTransformer
|
|||||||
//! Maximum value expected by the transformer
|
//! Maximum value expected by the transformer
|
||||||
double mMaxValue;
|
double mMaxValue;
|
||||||
|
|
||||||
|
//! Optional curve transform
|
||||||
|
std::unique_ptr< QgsCurveTransform > mCurveTransform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies base class numeric transformations. Derived classes should call this
|
||||||
|
* to transform an \a input numeric value before they apply any transform to the result.
|
||||||
|
* This applies any curve transforms which may exist on the transformer.
|
||||||
|
*/
|
||||||
|
double transformNumeric( double input ) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,6 +330,12 @@ class CORE_EXPORT QgsGenericNumericTransformer : public QgsPropertyTransformer
|
|||||||
double nullOutput = 0.0,
|
double nullOutput = 0.0,
|
||||||
double exponent = 1.0 );
|
double exponent = 1.0 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*/
|
||||||
|
QgsGenericNumericTransformer( const QgsGenericNumericTransformer& other );
|
||||||
|
QgsGenericNumericTransformer& operator=( const QgsGenericNumericTransformer& other );
|
||||||
|
|
||||||
virtual Type transformerType() const override { return GenericNumericTransformer; }
|
virtual Type transformerType() const override { return GenericNumericTransformer; }
|
||||||
virtual QgsGenericNumericTransformer* clone() override;
|
virtual QgsGenericNumericTransformer* clone() override;
|
||||||
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const override;
|
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const override;
|
||||||
@ -315,6 +466,12 @@ class CORE_EXPORT QgsSizeScaleTransformer : public QgsPropertyTransformer
|
|||||||
double nullSize = 0.0,
|
double nullSize = 0.0,
|
||||||
double exponent = 1.0 );
|
double exponent = 1.0 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*/
|
||||||
|
QgsSizeScaleTransformer( const QgsSizeScaleTransformer& other );
|
||||||
|
QgsSizeScaleTransformer& operator=( const QgsSizeScaleTransformer& other );
|
||||||
|
|
||||||
virtual Type transformerType() const override { return SizeScaleTransformer; }
|
virtual Type transformerType() const override { return SizeScaleTransformer; }
|
||||||
virtual QgsSizeScaleTransformer* clone() override;
|
virtual QgsSizeScaleTransformer* clone() override;
|
||||||
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const override;
|
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const override;
|
||||||
|
@ -3565,7 +3565,7 @@ QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate,
|
|||||||
return c.calculate( aggregate, fieldOrExpression, context, ok );
|
return c.calculate( aggregate, fieldOrExpression, context, ok );
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QVariant> QgsVectorLayer::getValues( const QString &fieldOrExpression, bool& ok, bool selectedOnly ) const
|
QList<QVariant> QgsVectorLayer::getValues( const QString &fieldOrExpression, bool& ok, bool selectedOnly, QgsFeedback* feedback ) const
|
||||||
{
|
{
|
||||||
QList<QVariant> values;
|
QList<QVariant> values;
|
||||||
|
|
||||||
@ -3623,12 +3623,17 @@ QList<QVariant> QgsVectorLayer::getValues( const QString &fieldOrExpression, boo
|
|||||||
{
|
{
|
||||||
values << f.attribute( attrNum );
|
values << f.attribute( attrNum );
|
||||||
}
|
}
|
||||||
|
if ( feedback && feedback->isCanceled() )
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
return values;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ok = true;
|
ok = true;
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<double> QgsVectorLayer::getDoubleValues( const QString &fieldOrExpression, bool& ok, bool selectedOnly, int* nullCount ) const
|
QList<double> QgsVectorLayer::getDoubleValues( const QString &fieldOrExpression, bool& ok, bool selectedOnly, int* nullCount, QgsFeedback* feedback ) const
|
||||||
{
|
{
|
||||||
QList<double> values;
|
QList<double> values;
|
||||||
|
|
||||||
@ -3650,6 +3655,11 @@ QList<double> QgsVectorLayer::getDoubleValues( const QString &fieldOrExpression,
|
|||||||
if ( nullCount )
|
if ( nullCount )
|
||||||
*nullCount += 1;
|
*nullCount += 1;
|
||||||
}
|
}
|
||||||
|
if ( feedback && feedback->isCanceled() )
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
return values;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ok = true;
|
ok = true;
|
||||||
return values;
|
return values;
|
||||||
|
@ -1497,11 +1497,12 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
|||||||
* @param fieldOrExpression field name or an expression string
|
* @param fieldOrExpression field name or an expression string
|
||||||
* @param ok will be set to false if field or expression is invalid, otherwise true
|
* @param ok will be set to false if field or expression is invalid, otherwise true
|
||||||
* @param selectedOnly set to true to get values from selected features only
|
* @param selectedOnly set to true to get values from selected features only
|
||||||
|
* @param feedback optional feedback object to allow cancelation
|
||||||
* @returns list of fetched values
|
* @returns list of fetched values
|
||||||
* @note added in QGIS 2.9
|
* @note added in QGIS 2.9
|
||||||
* @see getDoubleValues
|
* @see getDoubleValues
|
||||||
*/
|
*/
|
||||||
QList< QVariant > getValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false ) const;
|
QList< QVariant > getValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false, QgsFeedback* feedback = nullptr ) const;
|
||||||
|
|
||||||
/** Fetches all double values from a specified field name or expression. Null values or
|
/** Fetches all double values from a specified field name or expression. Null values or
|
||||||
* invalid expression results are skipped.
|
* invalid expression results are skipped.
|
||||||
@ -1509,11 +1510,12 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
|||||||
* @param ok will be set to false if field or expression is invalid, otherwise true
|
* @param ok will be set to false if field or expression is invalid, otherwise true
|
||||||
* @param selectedOnly set to true to get values from selected features only
|
* @param selectedOnly set to true to get values from selected features only
|
||||||
* @param nullCount optional pointer to integer to store number of null values encountered in
|
* @param nullCount optional pointer to integer to store number of null values encountered in
|
||||||
|
* @param feedback optional feedback object to allow cancelation
|
||||||
* @returns list of fetched values
|
* @returns list of fetched values
|
||||||
* @note added in QGIS 2.9
|
* @note added in QGIS 2.9
|
||||||
* @see getValues
|
* @see getValues
|
||||||
*/
|
*/
|
||||||
QList< double > getDoubleValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false, int* nullCount = nullptr ) const;
|
QList< double > getDoubleValues( const QString &fieldOrExpression, bool &ok, bool selectedOnly = false, int* nullCount = nullptr, QgsFeedback* feedback = nullptr ) const;
|
||||||
|
|
||||||
//! Set the blending mode used for rendering each feature
|
//! Set the blending mode used for rendering each feature
|
||||||
void setFeatureBlendMode( QPainter::CompositionMode blendMode );
|
void setFeatureBlendMode( QPainter::CompositionMode blendMode );
|
||||||
|
@ -193,6 +193,7 @@ SET(QGIS_GUI_SRCS
|
|||||||
qgscredentialdialog.cpp
|
qgscredentialdialog.cpp
|
||||||
qgscursors.cpp
|
qgscursors.cpp
|
||||||
qgscustomdrophandler.cpp
|
qgscustomdrophandler.cpp
|
||||||
|
qgscurveeditorwidget.cpp
|
||||||
qgsdatumtransformdialog.cpp
|
qgsdatumtransformdialog.cpp
|
||||||
qgsdetaileditemdata.cpp
|
qgsdetaileditemdata.cpp
|
||||||
qgsdetaileditemdelegate.cpp
|
qgsdetaileditemdelegate.cpp
|
||||||
@ -345,6 +346,7 @@ SET(QGIS_GUI_MOC_HDRS
|
|||||||
qgscompoundcolorwidget.h
|
qgscompoundcolorwidget.h
|
||||||
qgsconfigureshortcutsdialog.h
|
qgsconfigureshortcutsdialog.h
|
||||||
qgscredentialdialog.h
|
qgscredentialdialog.h
|
||||||
|
qgscurveeditorwidget.h
|
||||||
qgsdatumtransformdialog.h
|
qgsdatumtransformdialog.h
|
||||||
qgsdetaileditemdelegate.h
|
qgsdetaileditemdelegate.h
|
||||||
qgsdetaileditemwidget.h
|
qgsdetaileditemwidget.h
|
||||||
|
481
src/gui/qgscurveeditorwidget.cpp
Normal file
481
src/gui/qgscurveeditorwidget.cpp
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgscurveeditorwidget.cpp
|
||||||
|
------------------------
|
||||||
|
begin : February 2017
|
||||||
|
copyright : (C) 2017 by Nyall Dawson
|
||||||
|
email : nyall dot dawson at gmail dot com
|
||||||
|
***************************************************************************
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "qgscurveeditorwidget.h"
|
||||||
|
|
||||||
|
#include <qmath.h>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// QWT Charting widget
|
||||||
|
#include <qwt_global.h>
|
||||||
|
#include <qwt_plot_canvas.h>
|
||||||
|
#include <qwt_plot.h>
|
||||||
|
#include <qwt_plot_curve.h>
|
||||||
|
#include <qwt_plot_grid.h>
|
||||||
|
#include <qwt_plot_marker.h>
|
||||||
|
#include <qwt_plot_picker.h>
|
||||||
|
#include <qwt_picker_machine.h>
|
||||||
|
#include <qwt_plot_layout.h>
|
||||||
|
#include <qwt_symbol.h>
|
||||||
|
#include <qwt_legend.h>
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
#include <qwt_plot_renderer.h>
|
||||||
|
#include <qwt_plot_histogram.h>
|
||||||
|
#else
|
||||||
|
#include "../raster/qwt5_histogram_item.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QgsCurveEditorWidget::QgsCurveEditorWidget( QWidget* parent, const QgsCurveTransform& transform )
|
||||||
|
: QWidget( parent )
|
||||||
|
, mCurve( transform )
|
||||||
|
, mCurrentPlotMarkerIndex( -1 )
|
||||||
|
{
|
||||||
|
mPlot = new QwtPlot();
|
||||||
|
mPlot->setMinimumSize( QSize( 0, 100 ) );
|
||||||
|
mPlot->setAxisScale( QwtPlot::yLeft, 0, 1 );
|
||||||
|
mPlot->setAxisScale( QwtPlot::yRight, 0, 1 );
|
||||||
|
mPlot->setAxisScale( QwtPlot::xBottom, 0, 1 );
|
||||||
|
mPlot->setAxisScale( QwtPlot::xTop, 0, 1 );
|
||||||
|
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->addWidget( mPlot );
|
||||||
|
setLayout( vlayout );
|
||||||
|
|
||||||
|
// hide the ugly canvas frame
|
||||||
|
mPlot->setFrameStyle( QFrame::NoFrame );
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
QFrame* plotCanvasFrame = dynamic_cast<QFrame*>( mPlot->canvas() );
|
||||||
|
if ( plotCanvasFrame )
|
||||||
|
plotCanvasFrame->setFrameStyle( QFrame::NoFrame );
|
||||||
|
#else
|
||||||
|
mPlot->canvas()->setFrameStyle( QFrame::NoFrame );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mPlot->enableAxis( QwtPlot::yLeft, false );
|
||||||
|
mPlot->enableAxis( QwtPlot::xBottom, false );
|
||||||
|
|
||||||
|
// add a grid
|
||||||
|
QwtPlotGrid * grid = new QwtPlotGrid();
|
||||||
|
QwtScaleDiv gridDiv( 0.0, 1.0, QList<double>(), QList<double>(), QList<double>() << 0.2 << 0.4 << 0.6 << 0.8 );
|
||||||
|
grid->setXDiv( gridDiv );
|
||||||
|
grid->setYDiv( gridDiv );
|
||||||
|
grid->setPen( QPen( QColor( 0, 0, 0, 50 ) ) );
|
||||||
|
grid->attach( mPlot );
|
||||||
|
|
||||||
|
mPlotCurve = new QwtPlotCurve();
|
||||||
|
mPlotCurve->setTitle( QStringLiteral( "Curve" ) );
|
||||||
|
mPlotCurve->setPen( QPen( QColor( 30, 30, 30 ), 0.0 ) ),
|
||||||
|
mPlotCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
|
||||||
|
mPlotCurve->attach( mPlot );
|
||||||
|
|
||||||
|
mPlotFilter = new QgsCurveEditorPlotEventFilter( mPlot );
|
||||||
|
connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mousePress, this, &QgsCurveEditorWidget::plotMousePress );
|
||||||
|
connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mouseRelease, this, &QgsCurveEditorWidget::plotMouseRelease );
|
||||||
|
connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mouseMove, this, &QgsCurveEditorWidget::plotMouseMove );
|
||||||
|
|
||||||
|
mPlotCurve->setVisible( true );
|
||||||
|
updatePlot();
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsCurveEditorWidget::~QgsCurveEditorWidget()
|
||||||
|
{
|
||||||
|
if ( mGatherer && mGatherer->isRunning() )
|
||||||
|
{
|
||||||
|
connect( mGatherer.get(), &QgsHistogramValuesGatherer::finished, mGatherer.get(), &QgsHistogramValuesGatherer::deleteLater );
|
||||||
|
mGatherer->stop();
|
||||||
|
( void )mGatherer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::setCurve( const QgsCurveTransform& curve )
|
||||||
|
{
|
||||||
|
mCurve = curve;
|
||||||
|
updatePlot();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::setHistogramSource( const QgsVectorLayer* layer, const QString& expression )
|
||||||
|
{
|
||||||
|
if ( !mGatherer )
|
||||||
|
{
|
||||||
|
mGatherer.reset( new QgsHistogramValuesGatherer() );
|
||||||
|
connect( mGatherer.get(), &QgsHistogramValuesGatherer::calculatedHistogram, this, [=]
|
||||||
|
{
|
||||||
|
mHistogram.reset( new QgsHistogram( mGatherer->histogram() ) );
|
||||||
|
updateHistogram();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = mGatherer->layer() != layer || mGatherer->expression() != expression;
|
||||||
|
if ( changed )
|
||||||
|
{
|
||||||
|
mGatherer->setExpression( expression );
|
||||||
|
mGatherer->setLayer( layer );
|
||||||
|
mGatherer->start();
|
||||||
|
if ( mGatherer->isRunning() )
|
||||||
|
{
|
||||||
|
//stop any currently running task
|
||||||
|
mGatherer->stop();
|
||||||
|
while ( mGatherer->isRunning() )
|
||||||
|
{
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mGatherer->start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updateHistogram();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::setMinHistogramValueRange( double minValueRange )
|
||||||
|
{
|
||||||
|
mMinValueRange = minValueRange;
|
||||||
|
updateHistogram();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::setMaxHistogramValueRange( double maxValueRange )
|
||||||
|
{
|
||||||
|
mMaxValueRange = maxValueRange;
|
||||||
|
updateHistogram();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::keyPressEvent( QKeyEvent* event )
|
||||||
|
{
|
||||||
|
if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
|
||||||
|
{
|
||||||
|
QList< QgsPoint > cp = mCurve.controlPoints();
|
||||||
|
if ( mCurrentPlotMarkerIndex > 0 && mCurrentPlotMarkerIndex < cp.count() - 1 )
|
||||||
|
{
|
||||||
|
cp.removeAt( mCurrentPlotMarkerIndex );
|
||||||
|
mCurve.setControlPoints( cp );
|
||||||
|
updatePlot();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::plotMousePress( QPointF point )
|
||||||
|
{
|
||||||
|
mCurrentPlotMarkerIndex = findNearestControlPoint( point );
|
||||||
|
if ( mCurrentPlotMarkerIndex < 0 )
|
||||||
|
{
|
||||||
|
// add a new point
|
||||||
|
mCurve.addControlPoint( point.x(), point.y() );
|
||||||
|
mCurrentPlotMarkerIndex = findNearestControlPoint( point );
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
updatePlot();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int QgsCurveEditorWidget::findNearestControlPoint( QPointF point ) const
|
||||||
|
{
|
||||||
|
double minDist = 3.0 / mPlot->width();
|
||||||
|
int currentPlotMarkerIndex = -1;
|
||||||
|
|
||||||
|
QList< QgsPoint > controlPoints = mCurve.controlPoints();
|
||||||
|
|
||||||
|
for ( int i = 0; i < controlPoints.count(); ++i )
|
||||||
|
{
|
||||||
|
QgsPoint currentPoint = controlPoints.at( i );
|
||||||
|
double currentDist;
|
||||||
|
currentDist = qPow( point.x() - currentPoint.x(), 2.0 ) + qPow( point.y() - currentPoint.y(), 2.0 );
|
||||||
|
if ( currentDist < minDist )
|
||||||
|
{
|
||||||
|
minDist = currentDist;
|
||||||
|
currentPlotMarkerIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentPlotMarkerIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::plotMouseRelease( QPointF )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::plotMouseMove( QPointF point )
|
||||||
|
{
|
||||||
|
if ( mCurrentPlotMarkerIndex < 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
QList< QgsPoint > cp = mCurve.controlPoints();
|
||||||
|
bool removePoint = false;
|
||||||
|
if ( mCurrentPlotMarkerIndex == 0 )
|
||||||
|
{
|
||||||
|
point.setX( qMin( point.x(), cp.at( 1 ).x() - 0.01 ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
removePoint = point.x() <= cp.at( mCurrentPlotMarkerIndex - 1 ).x();
|
||||||
|
}
|
||||||
|
if ( mCurrentPlotMarkerIndex == cp.count() - 1 )
|
||||||
|
{
|
||||||
|
point.setX( qMax( point.x(), cp.at( mCurrentPlotMarkerIndex - 1 ).x() + 0.01 ) );
|
||||||
|
removePoint = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
removePoint = removePoint || point.x() >= cp.at( mCurrentPlotMarkerIndex + 1 ).x();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( removePoint )
|
||||||
|
{
|
||||||
|
cp.removeAt( mCurrentPlotMarkerIndex );
|
||||||
|
mCurrentPlotMarkerIndex = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cp[ mCurrentPlotMarkerIndex ] = QgsPoint( point.x(), point.y() );
|
||||||
|
}
|
||||||
|
mCurve.setControlPoints( cp );
|
||||||
|
updatePlot();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::addPlotMarker( double x, double y, bool isSelected )
|
||||||
|
{
|
||||||
|
QColor borderColor( 0, 0, 0 );
|
||||||
|
|
||||||
|
QColor brushColor = isSelected ? borderColor : QColor( 255, 255, 255, 0 );
|
||||||
|
|
||||||
|
QwtPlotMarker *marker = new QwtPlotMarker();
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
marker->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 8, 8 ) ) );
|
||||||
|
#else
|
||||||
|
marker->setSymbol( QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 8, 8 ) ) );
|
||||||
|
#endif
|
||||||
|
marker->setValue( x, y );
|
||||||
|
marker->attach( mPlot );
|
||||||
|
marker->setRenderHint( QwtPlotItem::RenderAntialiased, true );
|
||||||
|
mMarkers << marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::updateHistogram()
|
||||||
|
{
|
||||||
|
if ( !mHistogram )
|
||||||
|
return;
|
||||||
|
|
||||||
|
//draw histogram
|
||||||
|
QBrush histoBrush( QColor( 0, 0, 0, 70 ) );
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
delete mPlotHistogram;
|
||||||
|
mPlotHistogram = createPlotHistogram( histoBrush );
|
||||||
|
QVector<QwtIntervalSample> dataHisto;
|
||||||
|
#else
|
||||||
|
delete mPlotHistogramItem;
|
||||||
|
mPlotHistogramItem = createHistoItem( histoBrush );
|
||||||
|
QwtArray<QwtDoubleInterval> intervalsHisto;
|
||||||
|
QwtArray<double> valuesHisto;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int bins = 40;
|
||||||
|
QList<double> edges = mHistogram->binEdges( bins );
|
||||||
|
QList<int> counts = mHistogram->counts( bins );
|
||||||
|
|
||||||
|
// scale counts to 0->1
|
||||||
|
double max = *std::max_element( counts.constBegin(), counts.constEnd() );
|
||||||
|
|
||||||
|
// scale bin edges to fit in 0->1 range
|
||||||
|
if ( !qgsDoubleNear( mMinValueRange, mMaxValueRange ) )
|
||||||
|
{
|
||||||
|
std::transform( edges.begin(), edges.end(), edges.begin(),
|
||||||
|
[this]( double d ) -> double { return ( d - mMinValueRange ) / ( mMaxValueRange - mMinValueRange ); } );
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int bin = 0; bin < bins; ++bin )
|
||||||
|
{
|
||||||
|
double binValue = counts.at( bin ) / max;
|
||||||
|
|
||||||
|
double upperEdge = edges.at( bin + 1 );
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
|
||||||
|
#else
|
||||||
|
intervalsHisto.append( QwtDoubleInterval( edges.at( bin ), upperEdge ) );
|
||||||
|
valuesHisto.append( double( binValue ) );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
mPlotHistogram->setSamples( dataHisto );
|
||||||
|
mPlotHistogram->attach( mPlot );
|
||||||
|
#else
|
||||||
|
mPlotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
|
||||||
|
mPlotHistogramItem->attach( mPlot );
|
||||||
|
#endif
|
||||||
|
mPlot->replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCurveEditorWidget::updatePlot()
|
||||||
|
{
|
||||||
|
// remove existing markers
|
||||||
|
Q_FOREACH ( QwtPlotMarker* marker, mMarkers )
|
||||||
|
{
|
||||||
|
marker->detach();
|
||||||
|
delete marker;
|
||||||
|
}
|
||||||
|
mMarkers.clear();
|
||||||
|
|
||||||
|
QPolygonF curvePoints;
|
||||||
|
QVector< double > x;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
Q_FOREACH ( const QgsPoint& point, mCurve.controlPoints() )
|
||||||
|
{
|
||||||
|
x << point.x();
|
||||||
|
addPlotMarker( point.x(), point.y(), mCurrentPlotMarkerIndex == i );
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//add extra intermediate points
|
||||||
|
|
||||||
|
for ( double p = 0; p <= 1.0; p += 0.01 )
|
||||||
|
{
|
||||||
|
x << p;
|
||||||
|
}
|
||||||
|
std::sort( x.begin(), x.end() );
|
||||||
|
QVector< double > y = mCurve.y( x );
|
||||||
|
|
||||||
|
for ( int j = 0; j < x.count(); ++j )
|
||||||
|
{
|
||||||
|
curvePoints << QPointF( x.at( j ), y.at( j ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
mPlotCurve->setSamples( curvePoints );
|
||||||
|
#else
|
||||||
|
mPlotCurve->setData( curvePoints );
|
||||||
|
#endif
|
||||||
|
mPlot->replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
QwtPlotHistogram* QgsCurveEditorWidget::createPlotHistogram( const QBrush& brush, const QPen& pen ) const
|
||||||
|
{
|
||||||
|
QwtPlotHistogram* histogram = new QwtPlotHistogram( QString() );
|
||||||
|
histogram->setBrush( brush );
|
||||||
|
if ( pen != Qt::NoPen )
|
||||||
|
{
|
||||||
|
histogram->setPen( pen );
|
||||||
|
}
|
||||||
|
else if ( brush.color().lightness() > 200 )
|
||||||
|
{
|
||||||
|
QPen p;
|
||||||
|
p.setColor( brush.color().darker( 150 ) );
|
||||||
|
p.setWidth( 0 );
|
||||||
|
p.setCosmetic( true );
|
||||||
|
histogram->setPen( p );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
histogram->setPen( QPen( Qt::NoPen ) );
|
||||||
|
}
|
||||||
|
return histogram;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
HistogramItem * QgsCurveEditorWidget::createHistoItem( const QBrush& brush, const QPen& pen ) const
|
||||||
|
{
|
||||||
|
HistogramItem* item = new HistogramItem( QString() );
|
||||||
|
item->setColor( brush.color() );
|
||||||
|
item->setFlat( true );
|
||||||
|
item->setSpacing( 0 );
|
||||||
|
if ( pen != Qt::NoPen )
|
||||||
|
{
|
||||||
|
item->setPen( pen );
|
||||||
|
}
|
||||||
|
else if ( brush.color().lightness() > 200 )
|
||||||
|
{
|
||||||
|
QPen p;
|
||||||
|
p.setColor( brush.color().darker( 150 ) );
|
||||||
|
p.setWidth( 0 );
|
||||||
|
p.setCosmetic( true );
|
||||||
|
item->setPen( p );
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// @cond PRIVATE
|
||||||
|
|
||||||
|
QgsCurveEditorPlotEventFilter::QgsCurveEditorPlotEventFilter( QwtPlot *plot )
|
||||||
|
: QObject( plot )
|
||||||
|
, mPlot( plot )
|
||||||
|
{
|
||||||
|
mPlot->canvas()->installEventFilter( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsCurveEditorPlotEventFilter::eventFilter( QObject *object, QEvent *event )
|
||||||
|
{
|
||||||
|
if ( !mPlot->isEnabled() )
|
||||||
|
return QObject::eventFilter( object, event );
|
||||||
|
|
||||||
|
switch ( event->type() )
|
||||||
|
{
|
||||||
|
case QEvent::MouseButtonPress:
|
||||||
|
{
|
||||||
|
const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
|
||||||
|
if ( mouseEvent->button() == Qt::LeftButton )
|
||||||
|
{
|
||||||
|
emit mousePress( mapPoint( mouseEvent->pos() ) );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QEvent::MouseMove:
|
||||||
|
{
|
||||||
|
const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
|
||||||
|
if ( mouseEvent->buttons() & Qt::LeftButton )
|
||||||
|
{
|
||||||
|
// only emit when button pressed
|
||||||
|
emit mouseMove( mapPoint( mouseEvent->pos() ) );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QEvent::MouseButtonRelease:
|
||||||
|
{
|
||||||
|
const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
|
||||||
|
if ( mouseEvent->button() == Qt::LeftButton )
|
||||||
|
{
|
||||||
|
emit mouseRelease( mapPoint( mouseEvent->pos() ) );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QObject::eventFilter( object, event );
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointF QgsCurveEditorPlotEventFilter::mapPoint( QPointF point ) const
|
||||||
|
{
|
||||||
|
if ( !mPlot )
|
||||||
|
return QPointF();
|
||||||
|
|
||||||
|
return QPointF( mPlot->canvasMap( QwtPlot::xBottom ).invTransform( point.x() ),
|
||||||
|
mPlot->canvasMap( QwtPlot::yLeft ).invTransform( point.y() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///@endcond
|
282
src/gui/qgscurveeditorwidget.h
Normal file
282
src/gui/qgscurveeditorwidget.h
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgscurveeditorwidget.h
|
||||||
|
----------------------
|
||||||
|
begin : February 2017
|
||||||
|
copyright : (C) 2017 by Nyall Dawson
|
||||||
|
email : nyall dot dawson at gmail dot com
|
||||||
|
***************************************************************************
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QGSCURVEEDITORWIDGET_H
|
||||||
|
#define QGSCURVEEDITORWIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <qwt_global.h>
|
||||||
|
#include "qgis_gui.h"
|
||||||
|
#include "qgspropertytransformer.h"
|
||||||
|
#include "qgshistogram.h"
|
||||||
|
#include "qgsvectorlayer.h"
|
||||||
|
|
||||||
|
class QwtPlot;
|
||||||
|
class QwtPlotCurve;
|
||||||
|
class QwtPlotMarker;
|
||||||
|
class QwtPlotHistogram;
|
||||||
|
class HistogramItem;
|
||||||
|
class QgsCurveEditorPlotEventFilter;
|
||||||
|
|
||||||
|
// fix for qwt5/qwt6 QwtDoublePoint vs. QPointF
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
typedef QPointF QwtDoublePoint;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// just internal guff - definitely not for exposing to public API!
|
||||||
|
///@cond PRIVATE
|
||||||
|
|
||||||
|
/** \class QgsHistogramValuesGatherer
|
||||||
|
* Calculates a histogram in a thread.
|
||||||
|
*/
|
||||||
|
class QgsHistogramValuesGatherer: public QThread
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
QgsHistogramValuesGatherer() = default;
|
||||||
|
|
||||||
|
virtual void run() override
|
||||||
|
{
|
||||||
|
mWasCanceled = false;
|
||||||
|
if ( mExpression.isEmpty() || !mLayer )
|
||||||
|
{
|
||||||
|
mHistogram.setValues( QList<double>() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow responsive cancelation
|
||||||
|
mFeedback = new QgsFeedback();
|
||||||
|
|
||||||
|
mHistogram.setValues( mLayer, mExpression, mFeedback );
|
||||||
|
|
||||||
|
// be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
|
||||||
|
mFeedbackMutex.lock();
|
||||||
|
delete mFeedback;
|
||||||
|
mFeedback = nullptr;
|
||||||
|
mFeedbackMutex.unlock();
|
||||||
|
|
||||||
|
emit calculatedHistogram();
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Informs the gatherer to immediately stop collecting values
|
||||||
|
void stop()
|
||||||
|
{
|
||||||
|
// be cautious, in case gatherer stops naturally just as we are canceling it and mFeedback gets deleted
|
||||||
|
mFeedbackMutex.lock();
|
||||||
|
if ( mFeedback )
|
||||||
|
mFeedback->cancel();
|
||||||
|
mFeedbackMutex.unlock();
|
||||||
|
|
||||||
|
mWasCanceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Returns true if collection was canceled before completion
|
||||||
|
bool wasCanceled() const { return mWasCanceled; }
|
||||||
|
|
||||||
|
const QgsHistogram& histogram() const { return mHistogram; }
|
||||||
|
|
||||||
|
const QgsVectorLayer* layer() const
|
||||||
|
{
|
||||||
|
return mLayer;
|
||||||
|
}
|
||||||
|
void setLayer( const QgsVectorLayer* layer )
|
||||||
|
{
|
||||||
|
mLayer = const_cast< QgsVectorLayer* >( layer );
|
||||||
|
}
|
||||||
|
|
||||||
|
QString expression() const
|
||||||
|
{
|
||||||
|
return mExpression;
|
||||||
|
}
|
||||||
|
void setExpression( const QString& expression )
|
||||||
|
{
|
||||||
|
mExpression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when histogram has been calculated
|
||||||
|
*/
|
||||||
|
void calculatedHistogram();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QPointer< const QgsVectorLayer > mLayer = nullptr;
|
||||||
|
QString mExpression;
|
||||||
|
QgsHistogram mHistogram;
|
||||||
|
QgsFeedback* mFeedback = nullptr;
|
||||||
|
QMutex mFeedbackMutex;
|
||||||
|
bool mWasCanceled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
///@endcond
|
||||||
|
|
||||||
|
/** \ingroup gui
|
||||||
|
* \class QgsCurveEditorWidget
|
||||||
|
* A widget for manipulating QgsCurveTransform curves.
|
||||||
|
* \note added in QGIS 3.0
|
||||||
|
*/
|
||||||
|
class GUI_EXPORT QgsCurveEditorWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for QgsCurveEditorWidget.
|
||||||
|
*/
|
||||||
|
QgsCurveEditorWidget( QWidget* parent = nullptr, const QgsCurveTransform& curve = QgsCurveTransform() );
|
||||||
|
|
||||||
|
~QgsCurveEditorWidget();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a curve representing the current curve from the widget.
|
||||||
|
* @see setCurve()
|
||||||
|
*/
|
||||||
|
QgsCurveTransform curve() const { return mCurve; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the \a curve to show in the widget.
|
||||||
|
* @see curve()
|
||||||
|
*/
|
||||||
|
void setCurve( const QgsCurveTransform& curve );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a \a layer and \a expression source for values to show in a histogram
|
||||||
|
* behind the curve. The histogram is generated in a background thread to keep
|
||||||
|
* the widget responsive.
|
||||||
|
* @see minHistogramValueRange()
|
||||||
|
* @see maxHistogramValueRange()
|
||||||
|
*/
|
||||||
|
void setHistogramSource( const QgsVectorLayer* layer, const QString& expression );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum expected value for the range of values shown in the histogram.
|
||||||
|
* @see maxHistogramValueRange()
|
||||||
|
* @see setMinHistogramValueRange()
|
||||||
|
*/
|
||||||
|
double minHistogramValueRange() const { return mMinValueRange; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum expected value for the range of values shown in the histogram.
|
||||||
|
* @see minHistogramValueRange()
|
||||||
|
* @see setMaxHistogramValueRange()
|
||||||
|
*/
|
||||||
|
double maxHistogramValueRange() const { return mMaxValueRange; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum expected value for the range of values shown in the histogram.
|
||||||
|
* @see setMaxHistogramValueRange()
|
||||||
|
* @see minHistogramValueRange()
|
||||||
|
*/
|
||||||
|
void setMinHistogramValueRange( double minValueRange );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum expected value for the range of values shown in the histogram.
|
||||||
|
* @see setMinHistogramValueRange()
|
||||||
|
* @see maxHistogramValueRange()
|
||||||
|
*/
|
||||||
|
void setMaxHistogramValueRange( double maxValueRange );
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
//! Emitted when the widget curve changes
|
||||||
|
void changed();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void keyPressEvent( QKeyEvent *event ) override ;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void plotMousePress( QPointF point );
|
||||||
|
void plotMouseRelease( QPointF point );
|
||||||
|
void plotMouseMove( QPointF point );
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QgsCurveTransform mCurve;
|
||||||
|
|
||||||
|
QwtPlot* mPlot = nullptr;
|
||||||
|
|
||||||
|
QwtPlotCurve* mPlotCurve = nullptr;
|
||||||
|
|
||||||
|
QList< QwtPlotMarker* > mMarkers;
|
||||||
|
QgsCurveEditorPlotEventFilter* mPlotFilter = nullptr;
|
||||||
|
int mCurrentPlotMarkerIndex;
|
||||||
|
//! Background histogram gatherer thread
|
||||||
|
std::unique_ptr< QgsHistogramValuesGatherer > mGatherer;
|
||||||
|
std::unique_ptr< QgsHistogram > mHistogram;
|
||||||
|
double mMinValueRange = 0.0;
|
||||||
|
double mMaxValueRange = 1.0;
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
QwtPlotHistogram *mPlotHistogram = nullptr;
|
||||||
|
#else
|
||||||
|
HistogramItem *mPlotHistogramItem = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void updatePlot();
|
||||||
|
void addPlotMarker( double x, double y, bool isSelected = false );
|
||||||
|
void updateHistogram();
|
||||||
|
|
||||||
|
int findNearestControlPoint( QPointF point ) const;
|
||||||
|
|
||||||
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
|
QwtPlotHistogram* createPlotHistogram( const QBrush &brush, const QPen &pen = Qt::NoPen ) const;
|
||||||
|
#else
|
||||||
|
HistogramItem* createHistoItem( const QBrush& brush, const QPen& pen = Qt::NoPen ) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// NOTE:
|
||||||
|
// For private only, not part of stable api or exposed to Python bindings
|
||||||
|
//
|
||||||
|
/// @cond PRIVATE
|
||||||
|
class GUI_EXPORT QgsCurveEditorPlotEventFilter: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
QgsCurveEditorPlotEventFilter( QwtPlot *plot );
|
||||||
|
|
||||||
|
virtual bool eventFilter( QObject* object, QEvent* event ) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
void mousePress( QPointF );
|
||||||
|
void mouseRelease( QPointF );
|
||||||
|
void mouseMove( QPointF );
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QwtPlot* mPlot;
|
||||||
|
QPointF mapPoint( QPointF point ) const;
|
||||||
|
};
|
||||||
|
///@endcond
|
||||||
|
|
||||||
|
#endif // QGSCURVEEDITORWIDGET_H
|
@ -101,6 +101,14 @@ QgsGradientColorRampDialog::QgsGradientColorRampDialog( const QgsGradientColorRa
|
|||||||
mPlot->setAxisScale( QwtPlot::yLeft, 0.0, 1.0 );
|
mPlot->setAxisScale( QwtPlot::yLeft, 0.0, 1.0 );
|
||||||
mPlot->enableAxis( QwtPlot::yLeft, false );
|
mPlot->enableAxis( QwtPlot::yLeft, false );
|
||||||
|
|
||||||
|
// add a grid
|
||||||
|
QwtPlotGrid * grid = new QwtPlotGrid();
|
||||||
|
QwtScaleDiv gridDiv( 0.0, 1.0, QList<double>(), QList<double>(), QList<double>() << 0.2 << 0.4 << 0.6 << 0.8 );
|
||||||
|
grid->setXDiv( gridDiv );
|
||||||
|
grid->setYDiv( gridDiv );
|
||||||
|
grid->setPen( QPen( QColor( 0, 0, 0, 50 ) ) );
|
||||||
|
grid->attach( mPlot );
|
||||||
|
|
||||||
mLightnessCurve = new QwtPlotCurve();
|
mLightnessCurve = new QwtPlotCurve();
|
||||||
mLightnessCurve->setTitle( QStringLiteral( "Lightness" ) );
|
mLightnessCurve->setTitle( QStringLiteral( "Lightness" ) );
|
||||||
mLightnessCurve->setPen( QPen( QColor( 70, 150, 255 ), 0.0 ) ),
|
mLightnessCurve->setPen( QPen( QColor( 70, 150, 255 ), 0.0 ) ),
|
||||||
@ -453,9 +461,9 @@ void QgsGradientColorRampDialog::addPlotMarker( double x, double y, const QColor
|
|||||||
|
|
||||||
QwtPlotMarker *marker = new QwtPlotMarker();
|
QwtPlotMarker *marker = new QwtPlotMarker();
|
||||||
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
|
||||||
marker->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 10, 10 ) ) );
|
marker->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 8, 8 ) ) );
|
||||||
#else
|
#else
|
||||||
marker->setSymbol( QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 10, 10 ) ) );
|
marker->setSymbol( QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 8, 8 ) ) );
|
||||||
#endif
|
#endif
|
||||||
marker->setValue( x, y );
|
marker->setValue( x, y );
|
||||||
marker->attach( mPlot );
|
marker->attach( mPlot );
|
||||||
|
@ -51,6 +51,13 @@ QgsPropertyAssistantWidget::QgsPropertyAssistantWidget( QWidget* parent ,
|
|||||||
{
|
{
|
||||||
minValueSpinBox->setValue( initialState.transformer()->minValue() );
|
minValueSpinBox->setValue( initialState.transformer()->minValue() );
|
||||||
maxValueSpinBox->setValue( initialState.transformer()->maxValue() );
|
maxValueSpinBox->setValue( initialState.transformer()->maxValue() );
|
||||||
|
|
||||||
|
if ( initialState.transformer()->curveTransform() )
|
||||||
|
{
|
||||||
|
mTransformCurveCheckBox->setChecked( true );
|
||||||
|
mTransformCurveCheckBox->setCollapsed( false );
|
||||||
|
mCurveEditor->setCurve( *initialState.transformer()->curveTransform() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect( computeValuesButton, &QPushButton::clicked, this, &QgsPropertyAssistantWidget::computeValuesFromLayer );
|
connect( computeValuesButton, &QPushButton::clicked, this, &QgsPropertyAssistantWidget::computeValuesFromLayer );
|
||||||
@ -107,11 +114,25 @@ QgsPropertyAssistantWidget::QgsPropertyAssistantWidget( QWidget* parent ,
|
|||||||
{
|
{
|
||||||
mOutputWidget->layout()->addWidget( mTransformerWidget );
|
mOutputWidget->layout()->addWidget( mTransformerWidget );
|
||||||
connect( mTransformerWidget, &QgsPropertyAbstractTransformerWidget::widgetChanged, this, &QgsPropertyAssistantWidget::widgetChanged );
|
connect( mTransformerWidget, &QgsPropertyAbstractTransformerWidget::widgetChanged, this, &QgsPropertyAssistantWidget::widgetChanged );
|
||||||
|
|
||||||
|
mCurveEditor->setMinHistogramValueRange( minValueSpinBox->value() );
|
||||||
|
mCurveEditor->setMaxHistogramValueRange( maxValueSpinBox->value() );
|
||||||
|
|
||||||
|
mCurveEditor->setHistogramSource( mLayer, mExpressionWidget->currentField() );
|
||||||
|
connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString& ) > ( &QgsFieldExpressionWidget::fieldChanged ), this, [=]( const QString & expression )
|
||||||
|
{
|
||||||
|
mCurveEditor->setHistogramSource( mLayer, expression );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
connect( minValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), mCurveEditor, &QgsCurveEditorWidget::setMinHistogramValueRange );
|
||||||
|
connect( maxValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), mCurveEditor, &QgsCurveEditorWidget::setMaxHistogramValueRange );
|
||||||
}
|
}
|
||||||
|
mTransformCurveCheckBox->setVisible( mTransformerWidget );
|
||||||
|
|
||||||
connect( minValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
|
connect( minValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
|
||||||
connect( maxValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
|
connect( maxValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
|
||||||
connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString& ) > ( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
|
connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString& ) > ( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
|
||||||
|
connect( mCurveEditor, &QgsCurveEditorWidget::changed, this, &QgsPropertyAssistantWidget::widgetChanged );
|
||||||
connect( this, &QgsPropertyAssistantWidget::widgetChanged, this, &QgsPropertyAssistantWidget::updatePreview );
|
connect( this, &QgsPropertyAssistantWidget::widgetChanged, this, &QgsPropertyAssistantWidget::updatePreview );
|
||||||
updatePreview();
|
updatePreview();
|
||||||
}
|
}
|
||||||
@ -131,7 +152,18 @@ void QgsPropertyAssistantWidget::updateProperty( QgsProperty& property )
|
|||||||
property.setField( mExpressionWidget->currentField() );
|
property.setField( mExpressionWidget->currentField() );
|
||||||
|
|
||||||
if ( mTransformerWidget )
|
if ( mTransformerWidget )
|
||||||
property.setTransformer( mTransformerWidget->createTransformer( minValueSpinBox->value(), maxValueSpinBox->value() ) );
|
{
|
||||||
|
std::unique_ptr< QgsPropertyTransformer> t( mTransformerWidget->createTransformer( minValueSpinBox->value(), maxValueSpinBox->value() ) );
|
||||||
|
if ( mTransformCurveCheckBox->isChecked() )
|
||||||
|
{
|
||||||
|
t->setCurveTransform( new QgsCurveTransform( mCurveEditor->curve() ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
t->setCurveTransform( nullptr );
|
||||||
|
}
|
||||||
|
property.setTransformer( t.release() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsPropertyAssistantWidget::setDockMode( bool dockMode )
|
void QgsPropertyAssistantWidget::setDockMode( bool dockMode )
|
||||||
@ -166,6 +198,10 @@ void QgsPropertyAssistantWidget::computeValuesFromLayer()
|
|||||||
|
|
||||||
whileBlocking( minValueSpinBox )->setValue( minValue );
|
whileBlocking( minValueSpinBox )->setValue( minValue );
|
||||||
whileBlocking( maxValueSpinBox )->setValue( maxValue );
|
whileBlocking( maxValueSpinBox )->setValue( maxValue );
|
||||||
|
|
||||||
|
mCurveEditor->setMinHistogramValueRange( minValueSpinBox->value() );
|
||||||
|
mCurveEditor->setMaxHistogramValueRange( maxValueSpinBox->value() );
|
||||||
|
|
||||||
emit widgetChanged();
|
emit widgetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,8 +216,9 @@ void QgsPropertyAssistantWidget::updatePreview()
|
|||||||
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( minValueSpinBox->value(),
|
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( minValueSpinBox->value(),
|
||||||
maxValueSpinBox->value(), 8 );
|
maxValueSpinBox->value(), 8 );
|
||||||
|
|
||||||
|
QgsCurveTransform curve = mCurveEditor->curve();
|
||||||
QList< QgsSymbolLegendNode* > nodes = mTransformerWidget->generatePreviews( breaks, mLayerTreeLayer, mSymbol.get(), minValueSpinBox->value(),
|
QList< QgsSymbolLegendNode* > nodes = mTransformerWidget->generatePreviews( breaks, mLayerTreeLayer, mSymbol.get(), minValueSpinBox->value(),
|
||||||
maxValueSpinBox->value() );
|
maxValueSpinBox->value(), mTransformCurveCheckBox->isChecked() ? &curve : nullptr );
|
||||||
|
|
||||||
int widthMax = 0;
|
int widthMax = 0;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -353,7 +390,7 @@ QgsSizeScaleTransformer* QgsPropertySizeAssistantWidget::createTransformer( doub
|
|||||||
return transformer;
|
return transformer;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const
|
QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const
|
||||||
{
|
{
|
||||||
QList< QgsSymbolLegendNode* > nodes;
|
QList< QgsSymbolLegendNode* > nodes;
|
||||||
|
|
||||||
@ -376,6 +413,8 @@ QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews(
|
|||||||
return nodes;
|
return nodes;
|
||||||
|
|
||||||
std::unique_ptr< QgsSizeScaleTransformer > t( createTransformer( minValue, maxValue ) );
|
std::unique_ptr< QgsSizeScaleTransformer > t( createTransformer( minValue, maxValue ) );
|
||||||
|
if ( curve )
|
||||||
|
t->setCurveTransform( new QgsCurveTransform( *curve ) );
|
||||||
|
|
||||||
for ( int i = 0; i < breaks.length(); i++ )
|
for ( int i = 0; i < breaks.length(); i++ )
|
||||||
{
|
{
|
||||||
@ -401,7 +440,7 @@ QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews(
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QgsSymbolLegendNode*> QgsPropertyAbstractTransformerWidget::generatePreviews( const QList<double>& , QgsLayerTreeLayer* , const QgsSymbol*, double, double ) const
|
QList<QgsSymbolLegendNode*> QgsPropertyAbstractTransformerWidget::generatePreviews( const QList<double>& , QgsLayerTreeLayer* , const QgsSymbol*, double, double, QgsCurveTransform* ) const
|
||||||
{
|
{
|
||||||
return QList< QgsSymbolLegendNode* >();
|
return QList< QgsSymbolLegendNode* >();
|
||||||
}
|
}
|
||||||
@ -451,7 +490,7 @@ QgsColorRampTransformer* QgsPropertyColorAssistantWidget::createTransformer( dou
|
|||||||
return transformer;
|
return transformer;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QgsSymbolLegendNode*> QgsPropertyColorAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const
|
QList<QgsSymbolLegendNode*> QgsPropertyColorAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue , QgsCurveTransform* curve ) const
|
||||||
{
|
{
|
||||||
QList< QgsSymbolLegendNode* > nodes;
|
QList< QgsSymbolLegendNode* > nodes;
|
||||||
|
|
||||||
@ -467,6 +506,8 @@ QList<QgsSymbolLegendNode*> QgsPropertyColorAssistantWidget::generatePreviews( c
|
|||||||
return nodes;
|
return nodes;
|
||||||
|
|
||||||
std::unique_ptr< QgsColorRampTransformer > t( createTransformer( minValue, maxValue ) );
|
std::unique_ptr< QgsColorRampTransformer > t( createTransformer( minValue, maxValue ) );
|
||||||
|
if ( curve )
|
||||||
|
t->setCurveTransform( new QgsCurveTransform( *curve ) );
|
||||||
|
|
||||||
for ( int i = 0; i < breaks.length(); i++ )
|
for ( int i = 0; i < breaks.length(); i++ )
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,7 @@ class GUI_EXPORT QgsPropertyAbstractTransformerWidget : public QWidget
|
|||||||
|
|
||||||
virtual QgsPropertyTransformer* createTransformer( double minValue, double maxValue ) const = 0;
|
virtual QgsPropertyTransformer* createTransformer( double minValue, double maxValue ) const = 0;
|
||||||
|
|
||||||
virtual QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const;
|
virtual QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class GUI_EXPORT QgsPropertySizeAssistantWidget : public QgsPropertyAbstractTran
|
|||||||
|
|
||||||
virtual QgsSizeScaleTransformer* createTransformer( double minValue, double maxValue ) const override;
|
virtual QgsSizeScaleTransformer* createTransformer( double minValue, double maxValue ) const override;
|
||||||
|
|
||||||
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const override;
|
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GUI_EXPORT QgsPropertyColorAssistantWidget : public QgsPropertyAbstractTransformerWidget, private Ui::PropertyColorAssistant
|
class GUI_EXPORT QgsPropertyColorAssistantWidget : public QgsPropertyAbstractTransformerWidget, private Ui::PropertyColorAssistant
|
||||||
@ -95,7 +95,7 @@ class GUI_EXPORT QgsPropertyColorAssistantWidget : public QgsPropertyAbstractTra
|
|||||||
|
|
||||||
virtual QgsColorRampTransformer* createTransformer( double minValue, double maxValue ) const override;
|
virtual QgsColorRampTransformer* createTransformer( double minValue, double maxValue ) const override;
|
||||||
|
|
||||||
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const override;
|
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
///@endcond PRIVATE
|
///@endcond PRIVATE
|
||||||
|
@ -6,22 +6,12 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>522</width>
|
<width>525</width>
|
||||||
<height>332</height>
|
<height>426</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0">
|
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0">
|
||||||
<item row="0" column="1" rowspan="2">
|
<item row="2" column="0">
|
||||||
<widget class="QTreeView" name="mLegendPreview">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>200</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Output</string>
|
<string>Output</string>
|
||||||
@ -60,6 +50,16 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QFrame" name="mLegendVerticalFrame">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Plain</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -153,14 +153,60 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="0" column="1" rowspan="3">
|
||||||
<widget class="QFrame" name="mLegendVerticalFrame">
|
<widget class="QTreeView" name="mLegendPreview">
|
||||||
<property name="frameShape">
|
<property name="minimumSize">
|
||||||
<enum>QFrame::NoFrame</enum>
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="frameShadow">
|
</widget>
|
||||||
<enum>QFrame::Plain</enum>
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QgsCollapsibleGroupBoxBasic" name="mTransformCurveCheckBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Apply transform curve</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="collapsed" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="syncGroup" stdset="0">
|
||||||
|
<string notr="true">composeritem</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QgsCurveEditorWidget" name="mCurveEditor" native="true">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>100</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::StrongFocus</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -183,6 +229,18 @@
|
|||||||
<header>qgspanelwidget.h</header>
|
<header>qgspanelwidget.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>QgsCollapsibleGroupBoxBasic</class>
|
||||||
|
<extends>QGroupBox</extends>
|
||||||
|
<header location="global">qgscollapsiblegroupbox.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>QgsCurveEditorWidget</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header location="global">qgscurveeditorwidget.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>mExpressionWidget</tabstop>
|
<tabstop>mExpressionWidget</tabstop>
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include "qgstest.h"
|
#include "qgstest.h"
|
||||||
|
#include "qgstestutils.h"
|
||||||
#include "qgsproperty.h"
|
#include "qgsproperty.h"
|
||||||
#include "qgspropertycollection.h"
|
#include "qgspropertycollection.h"
|
||||||
#include "qgsvectorlayer.h"
|
#include "qgsvectorlayer.h"
|
||||||
@ -90,10 +91,12 @@ class TestQgsProperty : public QObject
|
|||||||
void asExpression(); //test converting property to expression
|
void asExpression(); //test converting property to expression
|
||||||
void propertyCollection(); //test for QgsPropertyCollection
|
void propertyCollection(); //test for QgsPropertyCollection
|
||||||
void collectionStack(); //test for QgsPropertyCollectionStack
|
void collectionStack(); //test for QgsPropertyCollectionStack
|
||||||
|
void curveTransform();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QgsPropertiesDefinition mDefinitions;
|
QgsPropertiesDefinition mDefinitions;
|
||||||
|
void checkCurveResult( const QList< QgsPoint >& controlPoints, const QVector<double>& x, const QVector<double>& y );
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -715,6 +718,26 @@ void TestQgsProperty::genericNumericTransformer()
|
|||||||
//non numeric value
|
//non numeric value
|
||||||
QCOMPARE( t1.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
|
QCOMPARE( t1.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
|
||||||
|
|
||||||
|
// add a curve
|
||||||
|
QVERIFY( !t1.curveTransform() );
|
||||||
|
t1.setCurveTransform( new QgsCurveTransform( QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) ) );
|
||||||
|
QVERIFY( t1.curveTransform() );
|
||||||
|
QCOMPARE( t1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
|
QCOMPARE( t1.transform( context, 10 ).toInt(), 180 );
|
||||||
|
QCOMPARE( t1.transform( context, 20 ).toInt(), 120 );
|
||||||
|
|
||||||
|
// copy
|
||||||
|
QgsGenericNumericTransformer s1( t1 );
|
||||||
|
QVERIFY( s1.curveTransform() );
|
||||||
|
QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
|
// assignment
|
||||||
|
QgsGenericNumericTransformer s2;
|
||||||
|
s2 = t1;
|
||||||
|
QVERIFY( s2.curveTransform() );
|
||||||
|
QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
//saving and restoring
|
//saving and restoring
|
||||||
|
|
||||||
//create a test dom element
|
//create a test dom element
|
||||||
@ -730,6 +753,7 @@ void TestQgsProperty::genericNumericTransformer()
|
|||||||
250,
|
250,
|
||||||
-10,
|
-10,
|
||||||
99 );
|
99 );
|
||||||
|
t2.setCurveTransform( new QgsCurveTransform( QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) ) );
|
||||||
|
|
||||||
QDomElement element = doc.createElement( "xform" );
|
QDomElement element = doc.createElement( "xform" );
|
||||||
QVERIFY( t2.writeXml( element, doc ) );
|
QVERIFY( t2.writeXml( element, doc ) );
|
||||||
@ -741,6 +765,8 @@ void TestQgsProperty::genericNumericTransformer()
|
|||||||
QCOMPARE( r1.maxOutputValue(), 250.0 );
|
QCOMPARE( r1.maxOutputValue(), 250.0 );
|
||||||
QCOMPARE( r1.nullOutputValue(), -10.0 );
|
QCOMPARE( r1.nullOutputValue(), -10.0 );
|
||||||
QCOMPARE( r1.exponent(), 99.0 );
|
QCOMPARE( r1.exponent(), 99.0 );
|
||||||
|
QVERIFY( r1.curveTransform() );
|
||||||
|
QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
// test cloning
|
// test cloning
|
||||||
std::unique_ptr< QgsGenericNumericTransformer > r2( t2.clone() );
|
std::unique_ptr< QgsGenericNumericTransformer > r2( t2.clone() );
|
||||||
@ -750,6 +776,8 @@ void TestQgsProperty::genericNumericTransformer()
|
|||||||
QCOMPARE( r2->maxOutputValue(), 250.0 );
|
QCOMPARE( r2->maxOutputValue(), 250.0 );
|
||||||
QCOMPARE( r2->nullOutputValue(), -10.0 );
|
QCOMPARE( r2->nullOutputValue(), -10.0 );
|
||||||
QCOMPARE( r2->exponent(), 99.0 );
|
QCOMPARE( r2->exponent(), 99.0 );
|
||||||
|
QVERIFY( r2->curveTransform() );
|
||||||
|
QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
//test various min/max value/size and scaling methods
|
//test various min/max value/size and scaling methods
|
||||||
|
|
||||||
@ -884,6 +912,25 @@ void TestQgsProperty::sizeScaleTransformer()
|
|||||||
//non numeric value
|
//non numeric value
|
||||||
QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
|
QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
|
||||||
|
|
||||||
|
// add a curve
|
||||||
|
QVERIFY( !scale.curveTransform() );
|
||||||
|
scale.setCurveTransform( new QgsCurveTransform( QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) ) );
|
||||||
|
QVERIFY( scale.curveTransform() );
|
||||||
|
QCOMPARE( scale.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) );
|
||||||
|
QCOMPARE( scale.transform( context, 10 ).toInt(), 120 );
|
||||||
|
QCOMPARE( scale.transform( context, 20 ).toInt(), 180 );
|
||||||
|
|
||||||
|
// copy
|
||||||
|
QgsSizeScaleTransformer s1( scale );
|
||||||
|
QVERIFY( s1.curveTransform() );
|
||||||
|
QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) );
|
||||||
|
|
||||||
|
// assignment
|
||||||
|
QgsSizeScaleTransformer s2;
|
||||||
|
s2 = scale;
|
||||||
|
QVERIFY( s2.curveTransform() );
|
||||||
|
QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) );
|
||||||
|
|
||||||
//saving and restoring
|
//saving and restoring
|
||||||
|
|
||||||
//create a test dom element
|
//create a test dom element
|
||||||
@ -900,6 +947,7 @@ void TestQgsProperty::sizeScaleTransformer()
|
|||||||
250,
|
250,
|
||||||
-10,
|
-10,
|
||||||
99 );
|
99 );
|
||||||
|
t1.setCurveTransform( new QgsCurveTransform( QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) ) );
|
||||||
|
|
||||||
QDomElement element = doc.createElement( "xform" );
|
QDomElement element = doc.createElement( "xform" );
|
||||||
QVERIFY( t1.writeXml( element, doc ) );
|
QVERIFY( t1.writeXml( element, doc ) );
|
||||||
@ -912,6 +960,8 @@ void TestQgsProperty::sizeScaleTransformer()
|
|||||||
QCOMPARE( r1.nullSize(), -10.0 );
|
QCOMPARE( r1.nullSize(), -10.0 );
|
||||||
QCOMPARE( r1.exponent(), 99.0 );
|
QCOMPARE( r1.exponent(), 99.0 );
|
||||||
QCOMPARE( r1.type(), QgsSizeScaleTransformer::Exponential );
|
QCOMPARE( r1.type(), QgsSizeScaleTransformer::Exponential );
|
||||||
|
QVERIFY( r1.curveTransform() );
|
||||||
|
QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
// test cloning
|
// test cloning
|
||||||
std::unique_ptr< QgsSizeScaleTransformer > r2( t1.clone() );
|
std::unique_ptr< QgsSizeScaleTransformer > r2( t1.clone() );
|
||||||
@ -922,6 +972,8 @@ void TestQgsProperty::sizeScaleTransformer()
|
|||||||
QCOMPARE( r2->nullSize(), -10.0 );
|
QCOMPARE( r2->nullSize(), -10.0 );
|
||||||
QCOMPARE( r2->exponent(), 99.0 );
|
QCOMPARE( r2->exponent(), 99.0 );
|
||||||
QCOMPARE( r2->type(), QgsSizeScaleTransformer::Exponential );
|
QCOMPARE( r2->type(), QgsSizeScaleTransformer::Exponential );
|
||||||
|
QVERIFY( r2->curveTransform() );
|
||||||
|
QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
//test various min/max value/size and scaling methods
|
//test various min/max value/size and scaling methods
|
||||||
|
|
||||||
@ -1089,6 +1141,26 @@ void TestQgsProperty::colorRampTransformer()
|
|||||||
//non numeric value
|
//non numeric value
|
||||||
QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
|
QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
|
||||||
|
|
||||||
|
// add a curve
|
||||||
|
QVERIFY( !scale.curveTransform() );
|
||||||
|
scale.setCurveTransform( new QgsCurveTransform( QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) ) );
|
||||||
|
QVERIFY( scale.curveTransform() );
|
||||||
|
QCOMPARE( scale.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) );
|
||||||
|
|
||||||
|
QCOMPARE( scale.transform( context, 10 ).value<QColor>().name(), QString( "#333333" ) );
|
||||||
|
QCOMPARE( scale.transform( context, 20 ).value<QColor>().name(), QString( "#cccccc" ) );
|
||||||
|
|
||||||
|
// copy
|
||||||
|
QgsColorRampTransformer s1( scale );
|
||||||
|
QVERIFY( s1.curveTransform() );
|
||||||
|
QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) );
|
||||||
|
|
||||||
|
// assignment
|
||||||
|
QgsColorRampTransformer s2;
|
||||||
|
s2 = scale;
|
||||||
|
QVERIFY( s2.curveTransform() );
|
||||||
|
QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1, 0.8 ) );
|
||||||
|
|
||||||
//saving and restoring
|
//saving and restoring
|
||||||
|
|
||||||
//create a test dom element
|
//create a test dom element
|
||||||
@ -1103,6 +1175,7 @@ void TestQgsProperty::colorRampTransformer()
|
|||||||
new QgsGradientColorRamp( QColor( 10, 20, 30 ), QColor( 200, 190, 180 ) ),
|
new QgsGradientColorRamp( QColor( 10, 20, 30 ), QColor( 200, 190, 180 ) ),
|
||||||
QColor( 100, 150, 200 ) );
|
QColor( 100, 150, 200 ) );
|
||||||
t1.setRampName( "rampname " );
|
t1.setRampName( "rampname " );
|
||||||
|
t1.setCurveTransform( new QgsCurveTransform( QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) ) );
|
||||||
|
|
||||||
QDomElement element = doc.createElement( "xform" );
|
QDomElement element = doc.createElement( "xform" );
|
||||||
QVERIFY( t1.writeXml( element, doc ) );
|
QVERIFY( t1.writeXml( element, doc ) );
|
||||||
@ -1115,6 +1188,8 @@ void TestQgsProperty::colorRampTransformer()
|
|||||||
QVERIFY( dynamic_cast< QgsGradientColorRamp* >( r1.colorRamp() ) );
|
QVERIFY( dynamic_cast< QgsGradientColorRamp* >( r1.colorRamp() ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r1.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r1.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r1.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r1.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
||||||
|
QVERIFY( r1.curveTransform() );
|
||||||
|
QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
// test cloning
|
// test cloning
|
||||||
std::unique_ptr< QgsColorRampTransformer > r2( t1.clone() );
|
std::unique_ptr< QgsColorRampTransformer > r2( t1.clone() );
|
||||||
@ -1124,6 +1199,8 @@ void TestQgsProperty::colorRampTransformer()
|
|||||||
QCOMPARE( r2->rampName(), QStringLiteral( "rampname " ) );
|
QCOMPARE( r2->rampName(), QStringLiteral( "rampname " ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r2->colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r2->colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r2->colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r2->colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
||||||
|
QVERIFY( r2->curveTransform() );
|
||||||
|
QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
// copy constructor
|
// copy constructor
|
||||||
QgsColorRampTransformer r3( t1 );
|
QgsColorRampTransformer r3( t1 );
|
||||||
@ -1133,6 +1210,8 @@ void TestQgsProperty::colorRampTransformer()
|
|||||||
QCOMPARE( r3.rampName(), QStringLiteral( "rampname " ) );
|
QCOMPARE( r3.rampName(), QStringLiteral( "rampname " ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r3.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r3.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r3.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r3.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
||||||
|
QVERIFY( r3.curveTransform() );
|
||||||
|
QCOMPARE( r3.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
// assignment operator
|
// assignment operator
|
||||||
QgsColorRampTransformer r4;
|
QgsColorRampTransformer r4;
|
||||||
@ -1143,6 +1222,8 @@ void TestQgsProperty::colorRampTransformer()
|
|||||||
QCOMPARE( r4.rampName(), QStringLiteral( "rampname " ) );
|
QCOMPARE( r4.rampName(), QStringLiteral( "rampname " ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r4.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r4.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
|
||||||
QCOMPARE( static_cast< QgsGradientColorRamp* >( r4.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
QCOMPARE( static_cast< QgsGradientColorRamp* >( r4.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
|
||||||
|
QVERIFY( r4.curveTransform() );
|
||||||
|
QCOMPARE( r4.curveTransform()->controlPoints(), QList< QgsPoint >() << QgsPoint( 0, 0.8 ) << QgsPoint( 1, 0.2 ) );
|
||||||
|
|
||||||
//test various min/max value/color and scaling methods
|
//test various min/max value/color and scaling methods
|
||||||
|
|
||||||
@ -1581,5 +1662,127 @@ void TestQgsProperty::collectionStack()
|
|||||||
QVERIFY( !stack4.hasDynamicProperties() );
|
QVERIFY( !stack4.hasDynamicProperties() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestQgsProperty::curveTransform()
|
||||||
|
{
|
||||||
|
QgsCurveTransform t;
|
||||||
|
// linear transform
|
||||||
|
QCOMPARE( t.y( -1 ), 0.0 );
|
||||||
|
QCOMPARE( t.y( 0 ), 0.0 );
|
||||||
|
QCOMPARE( t.y( 0.2 ), 0.2 );
|
||||||
|
QCOMPARE( t.y( 0.5 ), 0.5 );
|
||||||
|
QCOMPARE( t.y( 0.8 ), 0.8 );
|
||||||
|
QCOMPARE( t.y( 1 ), 1.0 );
|
||||||
|
QCOMPARE( t.y( 2 ), 1.0 );
|
||||||
|
|
||||||
|
QVector< double > x;
|
||||||
|
x << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2;
|
||||||
|
QVector< double > y = t.y( x );
|
||||||
|
QCOMPARE( y[0], 0.0 );
|
||||||
|
QCOMPARE( y[1], 0.0 );
|
||||||
|
QCOMPARE( y[2], 0.2 );
|
||||||
|
QCOMPARE( y[3], 0.5 );
|
||||||
|
QCOMPARE( y[4], 0.8 );
|
||||||
|
QCOMPARE( y[5], 1.0 );
|
||||||
|
QCOMPARE( y[6], 1.0 );
|
||||||
|
|
||||||
|
// linear transform with y =/= x
|
||||||
|
checkCurveResult( QList< QgsPoint >() << QgsPoint( 0, 0.2 ) << QgsPoint( 1.0, 0.8 ),
|
||||||
|
QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
|
||||||
|
QVector< double >() << 0.2 << 0.2 << 0.32 << 0.5 << 0.68 << 0.8 << 0.8 );
|
||||||
|
|
||||||
|
// reverse linear transform with y = -x
|
||||||
|
checkCurveResult( QList< QgsPoint >() << QgsPoint( 0.0, 1.0 ) << QgsPoint( 1.0, 0 ),
|
||||||
|
QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
|
||||||
|
QVector< double >() << 1.0 << 1.0 << 0.8 << 0.5 << 0.2 << 0.0 << 0.0 );
|
||||||
|
|
||||||
|
// ok, time for some more complex tests...
|
||||||
|
|
||||||
|
// 3 control points, but linear
|
||||||
|
checkCurveResult( QList< QgsPoint >() << QgsPoint( 0, 0.0 ) << QgsPoint( 0.2, 0.2 ) << QgsPoint( 1.0, 1.0 ),
|
||||||
|
QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
|
||||||
|
QVector< double >() << 0.0 << 0.0 << 0.2 << 0.5 << 0.8 << 1.0 << 1.0 );
|
||||||
|
|
||||||
|
// test for "flat" response for x outside of control point range
|
||||||
|
checkCurveResult( QList< QgsPoint >() << QgsPoint( 0.2, 0.2 ) << QgsPoint( 0.5, 0.5 ) << QgsPoint( 0.8, 0.8 ),
|
||||||
|
QVector< double >() << -1 << 0 << 0.1 << 0.2 << 0.5 << 0.8 << 0.9 << 1 << 2,
|
||||||
|
QVector< double >() << 0.2 << 0.2 << 0.2 << 0.2 << 0.5 << 0.8 << 0.8 << 0.8 << 0.8 );
|
||||||
|
|
||||||
|
//curves!
|
||||||
|
checkCurveResult( QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.4, 0.6 ) << QgsPoint( 0.6, 0.8 ) << QgsPoint( 1.0, 1.0 ),
|
||||||
|
QVector< double >() << -1 << 0 << 0.2 << 0.4 << 0.5 << 0.6 << 0.8 << 0.9 << 1.0 << 2.0,
|
||||||
|
QVector< double >() << 0.0 << 0.0 << 0.321429 << 0.6 << 0.710714 << 0.8 << 0.921429 << 0.963393 << 1.0 << 1.0 );
|
||||||
|
|
||||||
|
//curves with more control points
|
||||||
|
checkCurveResult( QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.2, 0.6 ) << QgsPoint( 0.4, 0.6 ) << QgsPoint( 0.6, 0.8 ) << QgsPoint( 0.8, 0.3 ) << QgsPoint( 1.0, 1.0 ),
|
||||||
|
QVector< double >() << -1 << 0 << 0.2 << 0.4 << 0.5 << 0.6 << 0.8 << 0.9 << 1.0 << 2.0,
|
||||||
|
QVector< double >() << 0.0 << 0.0 << 0.6 << 0.6 << 0.751316 << 0.8 << 0.3 << 0.508074 << 1.0 << 1.0 );
|
||||||
|
|
||||||
|
// general tests
|
||||||
|
QList< QgsPoint > points = QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.4, 0.6 ) << QgsPoint( 0.6, 0.8 ) << QgsPoint( 1.0, 1.0 );
|
||||||
|
QgsCurveTransform src( points );
|
||||||
|
QCOMPARE( src.controlPoints(), points );
|
||||||
|
points = QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.5, 0.6 ) << QgsPoint( 0.6, 0.8 ) << QgsPoint( 1.0, 1.0 );
|
||||||
|
src.setControlPoints( points );
|
||||||
|
QCOMPARE( src.controlPoints(), points );
|
||||||
|
|
||||||
|
src.setControlPoints( QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 1.0, 1.0 ) );
|
||||||
|
src.addControlPoint( 0.2, 0.3 );
|
||||||
|
src.addControlPoint( 0.1, 0.4 );
|
||||||
|
QCOMPARE( src.controlPoints(), QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.1, 0.4 ) << QgsPoint( 0.2, 0.3 ) << QgsPoint( 1.0, 1.0 ) );
|
||||||
|
|
||||||
|
// remove non-existent point
|
||||||
|
src.removeControlPoint( 0.6, 0.7 );
|
||||||
|
QCOMPARE( src.controlPoints(), QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.1, 0.4 ) << QgsPoint( 0.2, 0.3 ) << QgsPoint( 1.0, 1.0 ) );
|
||||||
|
|
||||||
|
// remove valid point
|
||||||
|
src.removeControlPoint( 0.1, 0.4 );
|
||||||
|
QCOMPARE( src.controlPoints(), QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.2, 0.3 ) << QgsPoint( 1.0, 1.0 ) );
|
||||||
|
|
||||||
|
// copy constructor
|
||||||
|
QgsCurveTransform dest( src );
|
||||||
|
QCOMPARE( dest.controlPoints(), QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.2, 0.3 ) << QgsPoint( 1.0, 1.0 ) );
|
||||||
|
// check a value to ensure that derivative matrix was copied ok
|
||||||
|
QGSCOMPARENEAR( dest.y( 0.5 ), 0.1, 0.638672 );
|
||||||
|
|
||||||
|
// assignment operator
|
||||||
|
QgsCurveTransform dest2;
|
||||||
|
dest2 = src;
|
||||||
|
QCOMPARE( dest2.controlPoints(), QList< QgsPoint >() << QgsPoint( 0.0, 0.0 ) << QgsPoint( 0.2, 0.3 ) << QgsPoint( 1.0, 1.0 ) );
|
||||||
|
QGSCOMPARENEAR( dest2.y( 0.5 ), 0.1, 0.638672 );
|
||||||
|
|
||||||
|
// writing and reading from xml
|
||||||
|
QDomImplementation DomImplementation;
|
||||||
|
QDomDocumentType documentType =
|
||||||
|
DomImplementation.createDocumentType(
|
||||||
|
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
|
||||||
|
QDomDocument doc( documentType );
|
||||||
|
|
||||||
|
QDomElement element = doc.createElement( "xform" );
|
||||||
|
QVERIFY( src.writeXml( element, doc ) );
|
||||||
|
|
||||||
|
QgsCurveTransform r1;
|
||||||
|
QVERIFY( r1.readXml( element, doc ) );
|
||||||
|
QCOMPARE( r1.controlPoints(), src.controlPoints() );
|
||||||
|
QGSCOMPARENEAR( dest2.y( 0.5 ), 0.1, 0.638672 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsProperty::checkCurveResult( const QList<QgsPoint>& controlPoints, const QVector<double>& x, const QVector<double>& y )
|
||||||
|
{
|
||||||
|
// build transform
|
||||||
|
QgsCurveTransform t( controlPoints );
|
||||||
|
|
||||||
|
// we check two approaches
|
||||||
|
for ( int i = 0; i < x.count(); ++i )
|
||||||
|
{
|
||||||
|
QGSCOMPARENEAR( t.y( x.at( i ) ), y.at( i ), 0.0001 );
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector< double > results = t.y( x );
|
||||||
|
for ( int i = 0; i < y.count(); ++i )
|
||||||
|
{
|
||||||
|
QGSCOMPARENEAR( results.at( i ), y.at( i ), 0.0001 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QGSTEST_MAIN( TestQgsProperty )
|
QGSTEST_MAIN( TestQgsProperty )
|
||||||
#include "testqgsproperty.moc"
|
#include "testqgsproperty.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user