diff --git a/src/KChart/Cartesian/KChartBarDiagram.cpp b/src/KChart/Cartesian/KChartBarDiagram.cpp index 7cbd6e6..482eb07 100644 --- a/src/KChart/Cartesian/KChartBarDiagram.cpp +++ b/src/KChart/Cartesian/KChartBarDiagram.cpp @@ -1,411 +1,359 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartBarDiagram.h" #include "KChartBarDiagram_p.h" #include "KChartThreeDBarAttributes.h" #include "KChartPosition.h" #include "KChartAttributesModel.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include #include #include "KChartNormalBarDiagram_p.h" #include "KChartStackedBarDiagram_p.h" #include "KChartPercentBarDiagram_p.h" #include "KChartNormalLyingBarDiagram_p.h" #include "KChartStackedLyingBarDiagram_p.h" #include "KChartPercentLyingBarDiagram_p.h" #include "KChartMath_p.h" using namespace KChart; BarDiagram::Private::Private() : orientation( Qt::Vertical ) , implementor( nullptr ) , normalDiagram( nullptr ) , stackedDiagram( nullptr ) , percentDiagram( nullptr ) , normalLyingDiagram( nullptr ) , stackedLyingDiagram( nullptr ) , percentLyingDiagram( nullptr ) { } BarDiagram::Private::~Private() { delete normalDiagram; delete stackedDiagram; delete percentDiagram; delete normalLyingDiagram; delete stackedLyingDiagram; delete percentLyingDiagram; } void BarDiagram::Private::setOrientationAndType( Qt::Orientation o, BarDiagram::BarType type ) { if ( orientation == o && implementor->type() == type ) { return; } BarDiagram *barDia = qobject_cast< BarDiagram * >( diagram ); orientation = o; if ( orientation == Qt::Vertical ) { switch ( type ) { case Normal: implementor = normalDiagram; break; case Stacked: implementor = stackedDiagram; break; case Percent: implementor = percentDiagram; break; default: Q_ASSERT_X( false, "BarDiagram::setType", "unknown diagram subtype" ); } } else { switch ( type ) { case Normal: implementor = normalLyingDiagram; break; case Stacked: implementor = stackedLyingDiagram; break; case Percent: implementor = percentLyingDiagram; break; default: Q_ASSERT_X( false, "BarDiagram::setType", "unknown diagram subtype" ); } } Q_ASSERT( implementor->type() == type ); // AbstractAxis settings - see AbstractDiagram and CartesianAxis barDia->setPercentMode( type == BarDiagram::Percent ); barDia->setDataBoundariesDirty(); emit barDia->layoutChanged( barDia ); emit barDia->propertiesChanged(); } #define d d_func() BarDiagram::BarDiagram( QWidget* parent, CartesianCoordinatePlane* plane ) : AbstractCartesianDiagram( new Private(), parent, plane ) { init(); } void BarDiagram::init() { d->normalDiagram = new NormalBarDiagram( this ); d->stackedDiagram = new StackedBarDiagram( this ); d->percentDiagram = new PercentBarDiagram( this ); d->normalLyingDiagram = new NormalLyingBarDiagram( this ); d->stackedLyingDiagram = new StackedLyingBarDiagram( this ); d->percentLyingDiagram = new PercentLyingBarDiagram( this ); d->implementor = d->normalDiagram; d->compressor.setModel( attributesModel() ); } BarDiagram::~BarDiagram() { } -/** - * Creates an exact copy of this diagram. - */ BarDiagram * BarDiagram::clone() const { BarDiagram* newDiagram = new BarDiagram( new Private( *d ) ); newDiagram->setType( type() ); return newDiagram; } bool BarDiagram::compare( const BarDiagram* other ) const { if ( other == this ) return true; if ( ! other ) { return false; } return // compare the base class ( static_cast(this)->compare( other ) ) && // compare own properties (type() == other->type()); } -/** - * Sets the bar diagram's type to \a type - * \sa BarDiagram::BarType - */ void BarDiagram::setType( const BarType type ) { d->setOrientationAndType( d->orientation, type ); } -/** - * @return the type of the bar diagram - */ BarDiagram::BarType BarDiagram::type() const { return d->implementor->type(); } -/** - * Sets the orientation of the bar diagram - */ void BarDiagram::setOrientation( Qt::Orientation orientation ) { d->setOrientationAndType( orientation, d->implementor->type() ); } -/** - * @return the orientation of the bar diagram - */ Qt::Orientation BarDiagram::orientation() const { return d->orientation; } -/** - * Sets the global bar attributes to \a ba - */ void BarDiagram::setBarAttributes( const BarAttributes& ba ) { d->attributesModel->setModelData( QVariant::fromValue( ba ), BarAttributesRole ); emit propertiesChanged(); } -/** - * Sets the bar attributes of data set \a column to \a ba - */ void BarDiagram::setBarAttributes( int column, const BarAttributes& ba ) { d->setDatasetAttrs( column, QVariant::fromValue( ba ), BarAttributesRole ); emit propertiesChanged(); } -/** - * Sets the line attributes for the model index \a index to \a ba - */ void BarDiagram::setBarAttributes( const QModelIndex& index, const BarAttributes& ba ) { attributesModel()->setData( d->attributesModel->mapFromSource( index ), QVariant::fromValue( ba ), BarAttributesRole ); emit propertiesChanged(); } -/** - * @return the global bar attribute set - */ BarAttributes BarDiagram::barAttributes() const { return d->attributesModel->data( KChart::BarAttributesRole ).value(); } -/** - * @return the bar attribute set of data set \a column - */ BarAttributes BarDiagram::barAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, KChart::BarAttributesRole ) ); if ( attrs.isValid() ) return attrs.value(); return barAttributes(); } -/** - * @return the bar attribute set of the model index \a index - */ BarAttributes BarDiagram::barAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::BarAttributesRole ).value(); } -/** - * Sets the global 3D bar attributes to \a threeDAttrs - */ void BarDiagram::setThreeDBarAttributes( const ThreeDBarAttributes& threeDAttrs ) { setDataBoundariesDirty(); d->attributesModel->setModelData( QVariant::fromValue( threeDAttrs ), ThreeDBarAttributesRole ); emit layoutChanged( this ); emit propertiesChanged(); } -/** - * Sets the 3D bar attributes of dataset \a column to \a threeDAttrs - */ void BarDiagram::setThreeDBarAttributes( int column, const ThreeDBarAttributes& threeDAttrs ) { setDataBoundariesDirty(); d->setDatasetAttrs( column, QVariant::fromValue( threeDAttrs ), ThreeDBarAttributesRole ); //emit layoutChanged( this ); emit propertiesChanged(); } -/** - * Sets the 3D line attributes of model index \a index to \a threeDAttrs - */ void BarDiagram::setThreeDBarAttributes( const QModelIndex& index, const ThreeDBarAttributes& threeDAttrs ) { setDataBoundariesDirty(); d->attributesModel->setData( d->attributesModel->mapFromSource(index), QVariant::fromValue( threeDAttrs ), ThreeDBarAttributesRole ); //emit layoutChanged( this ); emit propertiesChanged(); } -/** - * @return the global 3D bar attributes - */ ThreeDBarAttributes BarDiagram::threeDBarAttributes() const { return d->attributesModel->data( KChart::ThreeDBarAttributesRole ).value(); } -/** - * @return the 3D bar attributes of data set \a column - */ ThreeDBarAttributes BarDiagram::threeDBarAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, KChart::ThreeDBarAttributesRole ) ); if ( attrs.isValid() ) return attrs.value(); return threeDBarAttributes(); } -/** - * @return the 3D bar attributes of the model index \a index - */ ThreeDBarAttributes BarDiagram::threeDBarAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource(index), KChart::ThreeDBarAttributesRole ).value(); } qreal BarDiagram::threeDItemDepth( const QModelIndex& index ) const { return threeDBarAttributes( index ).validDepth(); } qreal BarDiagram::threeDItemDepth( int column ) const { return threeDBarAttributes( column ).validDepth(); } void BarDiagram::resizeEvent ( QResizeEvent*) { } const QPair BarDiagram::calculateDataBoundaries() const { d->compressor.setResolution( static_cast( this->size().width() * coordinatePlane()->zoomFactorX() ), static_cast( this->size().height() * coordinatePlane()->zoomFactorY() ) ); if ( !checkInvariants( true ) ) { return QPair< QPointF, QPointF >( QPointF( 0, 0 ), QPointF( 0, 0 ) ); } // note: calculateDataBoundaries() is ignoring the hidden flags. // That's not a bug but a feature: Hiding data does not mean removing them. // For totally removing data from KD Chart's view people can use e.g. a proxy model // calculate boundaries for different line types Normal - Stacked - Percent - Default Normal return d->implementor->calculateDataBoundaries(); } void BarDiagram::paintEvent ( QPaintEvent*) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void BarDiagram::paint( PaintContext* ctx ) { if ( !checkInvariants( true ) ) return; if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return; const PainterSaver p( ctx->painter() ); if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) == 0 ) return; // nothing to paint for us AbstractCoordinatePlane* const plane = ctx->coordinatePlane(); ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( ctx->painter() ) ); // This was intended as a fix for KDCH-515, however it caused KDCH-816 // and the original problem in KDCH-515 had by then been fixed in another way. // Bottom line is, this code is wrong because the above call to // plane->sharedAxisMasterPlane() performs a translation of the painter, which // also translates the clip rect, so if we set the old clip rect again afterwards, // we get a wrong clipping. // Also, this code is unnecessary because CartesianCoordinatePlane::paint() // already sets the clipping properly before calling this method. // ctx->painter()->setClipping( true ); // ctx->painter()->setClipRect( ctx->rectangle() ); // paint different bar types Normal - Stacked - Percent - Default Normal d->implementor->paint( ctx ); ctx->setCoordinatePlane( plane ); } void BarDiagram::resize( const QSizeF& size ) { d->compressor.setResolution( static_cast< int >( size.width() * coordinatePlane()->zoomFactorX() ), static_cast< int >( size.height() * coordinatePlane()->zoomFactorY() ) ); setDataBoundariesDirty(); AbstractCartesianDiagram::resize( size ); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int BarDiagram::numberOfAbscissaSegments () const { return d->attributesModel->rowCount(attributesModelRootIndex()); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int BarDiagram::numberOfOrdinateSegments () const { return d->attributesModel->columnCount(attributesModelRootIndex()); } //#undef d diff --git a/src/KChart/Cartesian/KChartBarDiagram.h b/src/KChart/Cartesian/KChartBarDiagram.h index 78a641a..2554980 100644 --- a/src/KChart/Cartesian/KChartBarDiagram.h +++ b/src/KChart/Cartesian/KChartBarDiagram.h @@ -1,127 +1,195 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTBARDIAGRAM_H #define KCHARTBARDIAGRAM_H #include "KChartAbstractCartesianDiagram.h" #include "KChartBarAttributes.h" QT_BEGIN_NAMESPACE class QPainter; QT_END_NAMESPACE namespace KChart { class ThreeDBarAttributes; /** * @brief BarDiagram defines a common bar diagram. * * It provides different subtypes which are set using \a setType. */ class KCHART_EXPORT BarDiagram : public AbstractCartesianDiagram { Q_OBJECT Q_DISABLE_COPY( BarDiagram ) KCHART_DECLARE_DERIVED_DIAGRAM( BarDiagram, CartesianCoordinatePlane ) public: class BarDiagramType; friend class BarDiagramType; explicit BarDiagram( QWidget* parent = nullptr, CartesianCoordinatePlane* plane = nullptr ); virtual ~BarDiagram(); - virtual BarDiagram * clone() const; + + /** + * Creates an exact copy of this diagram. + */ + virtual BarDiagram * clone() const; /** * Returns true if both diagrams have the same settings. */ bool compare( const BarDiagram* other ) const; enum BarType { Normal, Stacked, Percent, Rows ///< @deprecated Use BarDiagram::setOrientation() instead }; + + /** + * Sets the bar diagram's type to \a type + * \sa BarDiagram::BarType + */ void setType( const BarType type ); + + /** + * @return the type of the bar diagram + */ BarType type() const; + + /** + * Sets the orientation of the bar diagram + */ void setOrientation( Qt::Orientation orientation ); + + /** + * @return the orientation of the bar diagram + */ Qt::Orientation orientation() const; + + /** + * Sets the global bar attributes to \a ba + */ void setBarAttributes( const BarAttributes & a ); + + /** + * Sets the bar attributes of data set \a column to \a ba + */ void setBarAttributes( int column, const BarAttributes & a ); + + /** + * Sets the line attributes for the model index \a index to \a ba + */ void setBarAttributes( const QModelIndex & index, const BarAttributes & a ); + /** + * @return the global bar attribute set + */ BarAttributes barAttributes() const; + + /** + * @return the bar attribute set of data set \a column + */ BarAttributes barAttributes( int column ) const; + + /** + * @return the bar attribute set of the model index \a index + */ BarAttributes barAttributes( const QModelIndex & index ) const; + + /** + * Sets the global 3D bar attributes to \a threeDAttrs + */ void setThreeDBarAttributes( const ThreeDBarAttributes & a ); + + /** + * Sets the 3D bar attributes of dataset \a column to \a threeDAttrs + */ void setThreeDBarAttributes( int column, const ThreeDBarAttributes & a ); + + /** + * Sets the 3D line attributes of model index \a index to \a threeDAttrs + */ void setThreeDBarAttributes( const QModelIndex & index, const ThreeDBarAttributes & a ); + + /** + * @return the global 3D bar attributes + */ ThreeDBarAttributes threeDBarAttributes() const; + + /** + * @return the 3D bar attributes of data set \a column + */ ThreeDBarAttributes threeDBarAttributes( int column ) const; + + /** + * @return the 3D bar attributes of the model index \a index + */ ThreeDBarAttributes threeDBarAttributes( const QModelIndex & index ) const; #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) // implement AbstractCartesianDiagram /** \reimpl */ const int numberOfAbscissaSegments () const; /** \reimpl */ const int numberOfOrdinateSegments () const; #else // implement AbstractCartesianDiagram /** \reimpl */ int numberOfAbscissaSegments () const override; /** \reimpl */ int numberOfOrdinateSegments () const override; #endif protected: void paint ( PaintContext* paintContext ) override; public: void resize ( const QSizeF& area ) override; protected: qreal threeDItemDepth( const QModelIndex & index ) const override; qreal threeDItemDepth( int column ) const override; /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent ( QPaintEvent* ) override; void resizeEvent ( QResizeEvent* ) override; private: void calculateValueAndGapWidths( int rowCount, int colCount, qreal groupWidth, qreal& barWidth, qreal& spaceBetweenBars, qreal& spaceBetweenGroups ); }; // End of class BarDiagram } #endif // KCHARTBARDIAGRAM_H diff --git a/src/KChart/Cartesian/KChartLeveyJenningsAxis.cpp b/src/KChart/Cartesian/KChartLeveyJenningsAxis.cpp index e1e80dc..659df32 100644 --- a/src/KChart/Cartesian/KChartLeveyJenningsAxis.cpp +++ b/src/KChart/Cartesian/KChartLeveyJenningsAxis.cpp @@ -1,271 +1,259 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLeveyJenningsAxis.h" #include "KChartLeveyJenningsAxis_p.h" #include #include #include "KChartPaintContext.h" #include "KChartChart.h" #include "KChartAbstractCartesianDiagram.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include "KChartLayoutItems.h" #include "KChartPrintingParameters.h" #include "KChartMath_p.h" using namespace KChart; #define d (d_func()) LeveyJenningsAxis::LeveyJenningsAxis ( LeveyJenningsDiagram* diagram ) : CartesianAxis ( new Private( diagram, this ), diagram ) { init(); } LeveyJenningsAxis::~LeveyJenningsAxis () { // when we remove the first axis it will unregister itself and // propagate the next one to the primary, thus the while loop while ( d->mDiagram ) { LeveyJenningsDiagram *cd = qobject_cast< LeveyJenningsDiagram* >( d->mDiagram ); cd->takeAxis( this ); } Q_FOREACH( AbstractDiagram *diagram, d->secondaryDiagrams ) { LeveyJenningsDiagram *cd = qobject_cast< LeveyJenningsDiagram* >( diagram ); cd->takeAxis( this ); } } void LeveyJenningsAxis::init () { setType( LeveyJenningsGridAttributes::Expected ); setDateFormat( Qt::TextDate ); const QStringList labels = QStringList() << tr( "-3sd" ) << tr( "-2sd" ) << tr( "mean" ) << tr( "+2sd" ) << tr( "+3sd" ); setLabels( labels ); } -/** - * @return The axis' type. - */ LeveyJenningsGridAttributes::GridType LeveyJenningsAxis::type() const { return d->type; } -/** - * Sets the type of the axis to \a type. - * This method colors the label to the default color of the - * respective type. - * Please make sure to re-set the colors after calling this, - * if you want them different. - * Setting the type is only valid for axes located right or left - * from the diagram. An axis on the bottom always shows the timeline. - */ void LeveyJenningsAxis::setType( LeveyJenningsGridAttributes::GridType type ) { if ( type != d->type ) { TextAttributes ta = textAttributes(); QPen pen = ta.pen(); QColor color = type == LeveyJenningsGridAttributes::Expected ? Qt::black : Qt::blue; if ( qobject_cast< const LeveyJenningsDiagram* >( d->diagram() ) && qobject_cast< const LeveyJenningsCoordinatePlane* >( d->diagram()->coordinatePlane() ) ) { color = qobject_cast< const LeveyJenningsCoordinatePlane* >( d->diagram()->coordinatePlane() )->gridAttributes().gridPen( type ).color(); } pen.setColor( color ); ta.setPen( pen ); setTextAttributes( ta ); } d->type = type; } Qt::DateFormat LeveyJenningsAxis::dateFormat() const { return d->format; } void LeveyJenningsAxis::setDateFormat(Qt::DateFormat format) { d->format = format; } bool LeveyJenningsAxis::compare( const LeveyJenningsAxis* other ) const { if ( other == this ) return true; if ( ! other ) { //qDebug() << "CartesianAxis::compare() cannot compare to Null pointer"; return false; } return ( static_cast(this)->compare( other ) ) && ( type() == other->type() ); } void LeveyJenningsAxis::paintCtx( PaintContext* context ) { Q_ASSERT_X ( d->diagram(), "LeveyJenningsAxis::paint", "Function call not allowed: The axis is not assigned to any diagram." ); LeveyJenningsCoordinatePlane* plane = dynamic_cast(context->coordinatePlane()); Q_ASSERT_X ( plane, "LeveyJenningsAxis::paint", "Bad function call: PaintContext::coodinatePlane() NOT a levey jennings plane." ); Q_UNUSED(plane); // note: Not having any data model assigned is no bug // but we can not draw an axis then either. if ( ! d->diagram()->model() ) return; if ( isOrdinate() ) paintAsOrdinate( context ); else paintAsAbscissa( context ); } void LeveyJenningsAxis::paintAsOrdinate( PaintContext* context ) { const LeveyJenningsDiagram* const diag = dynamic_cast< const LeveyJenningsDiagram* >( d->diagram() ); Q_ASSERT( isOrdinate() ); LeveyJenningsCoordinatePlane* plane = dynamic_cast(context->coordinatePlane()); const qreal meanValue = type() == LeveyJenningsGridAttributes::Expected ? diag->expectedMeanValue() : diag->calculatedMeanValue(); const qreal standardDeviation = type() == LeveyJenningsGridAttributes::Expected ? diag->expectedStandardDeviation() : diag->calculatedStandardDeviation(); const TextAttributes labelTA = textAttributes(); const bool drawLabels = labelTA.isVisible(); // nothing to draw, since we've no ticks if ( !drawLabels ) return; const QObject* referenceArea = plane->parent(); const QVector< qreal > values = QVector< qreal >() << ( meanValue - 3 * standardDeviation ) << ( meanValue - 2 * standardDeviation ) << ( meanValue ) << ( meanValue + 2 * standardDeviation ) << ( meanValue + 3 * standardDeviation ); Q_ASSERT_X( values.count() <= labels().count(), "LeveyJenningsAxis::paintAsOrdinate", "Need to have at least 5 labels" ); TextLayoutItem labelItem( tr( "mean" ), labelTA, referenceArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); QPainter* const painter = context->painter(); const PainterSaver ps( painter ); painter->setRenderHint( QPainter::Antialiasing, true ); painter->setClipping( false ); painter->setPen ( PrintingParameters::scalePen( labelTA.pen() ) ); // perhaps we want to add a setter method later? for ( int i = 0; i < values.count(); ++i ) { const QPointF labelPos = plane->translate( QPointF( 0.0, values.at( i ) ) ); const QString label = customizedLabel( labels().at( i ) ); labelItem.setText( label ); const QSize size = labelItem.sizeHint(); const float xPos = position() == Left ? geometry().right() - size.width() : geometry().left(); labelItem.setGeometry( QRectF( QPointF( xPos, labelPos.y() - size.height() / 2.0 ), size ).toRect() ); // don't draw labels which aren't in the valid range (might happen for calculated SDs) if ( values.at( i ) > diag->expectedMeanValue() + 4 * diag->expectedStandardDeviation() ) continue; if ( values.at( i ) < diag->expectedMeanValue() - 4 * diag->expectedStandardDeviation() ) continue; labelItem.paint( painter ); } } void LeveyJenningsAxis::paintAsAbscissa( PaintContext* context ) { Q_ASSERT( isAbscissa() ); // this triggers drawing of the ticks setLabels( QStringList() << QString::fromLatin1( " " ) ); CartesianAxis::paintCtx( context ); const LeveyJenningsDiagram* const diag = dynamic_cast< const LeveyJenningsDiagram* >( d->diagram() ); LeveyJenningsCoordinatePlane* plane = dynamic_cast(context->coordinatePlane()); const QObject* referenceArea = plane->parent(); const TextAttributes labelTA = textAttributes(); const bool drawLabels = labelTA.isVisible(); if ( !drawLabels ) return; const QPair< QDateTime, QDateTime > range = diag->timeRange(); QPainter* const painter = context->painter(); const PainterSaver ps( painter ); painter->setRenderHint( QPainter::Antialiasing, true ); painter->setClipping( false ); TextLayoutItem labelItem( range.first.date().toString( dateFormat() ), labelTA, referenceArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); QSize origSize = labelItem.sizeHint(); if ( range.first.secsTo( range.second ) < 86400 ) labelItem = TextLayoutItem( range.first.toString( dateFormat() ), labelTA, referenceArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); QSize size = labelItem.sizeHint(); float yPos = position() == Bottom ? geometry().bottom() - size.height() : geometry().top(); labelItem.setGeometry( QRectF( QPointF( geometry().left() - origSize.width() / 2.0, yPos ), size ).toRect() ); labelItem.paint( painter ); TextLayoutItem labelItem2( range.second.date().toString( dateFormat() ), labelTA, referenceArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); origSize = labelItem2.sizeHint(); if ( range.first.secsTo( range.second ) < 86400 ) labelItem2 = TextLayoutItem( range.second.toString( dateFormat() ), labelTA, referenceArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); size = labelItem2.sizeHint(); yPos = position() == Bottom ? geometry().bottom() - size.height() : geometry().top(); labelItem2.setGeometry( QRectF( QPointF( geometry().right() - size.width() + origSize.width() / 2.0, yPos ), size ).toRect() ); labelItem2.paint( painter ); } diff --git a/src/KChart/Cartesian/KChartLeveyJenningsAxis.h b/src/KChart/Cartesian/KChartLeveyJenningsAxis.h index 7c0b642..958b44f 100644 --- a/src/KChart/Cartesian/KChartLeveyJenningsAxis.h +++ b/src/KChart/Cartesian/KChartLeveyJenningsAxis.h @@ -1,85 +1,98 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTLEVEYJENNINGSAXIS_H #define KCHARTLEVEYJENNINGSAXIS_H #include #include "KChartCartesianAxis.h" #include "KChartLeveyJenningsGridAttributes.h" namespace KChart { class LeveyJenningsDiagram; /** * The class for levey jennings axes. * * For being useful, axes need to be assigned to a diagram, see * LeveyJenningsDiagram::addAxis and LeveyJenningsDiagram::takeAxis. * * \sa PolarAxis, AbstractCartesianDiagram */ class KCHART_EXPORT LeveyJenningsAxis : public CartesianAxis { Q_OBJECT Q_DISABLE_COPY( LeveyJenningsAxis ) KCHART_DECLARE_PRIVATE_DERIVED_PARENT( LeveyJenningsAxis, AbstractDiagram* ) public: /** * C'tor of the class for levey jennings axes. * * \note If using a zero parent for the constructor, you need to call * your diagram's addAxis function to add your axis to the diagram. * Otherwise, there is no need to call addAxis, since the constructor * does that automatically for you, if you pass a diagram as parameter. * * \sa AbstractCartesianDiagram::addAxis */ explicit LeveyJenningsAxis ( LeveyJenningsDiagram* diagram = nullptr ); ~LeveyJenningsAxis(); + /** + * @return The axis' type. + */ LeveyJenningsGridAttributes::GridType type() const; + + /** + * Sets the type of the axis to \a type. + * This method colors the label to the default color of the + * respective type. + * Please make sure to re-set the colors after calling this, + * if you want them different. + * Setting the type is only valid for axes located right or left + * from the diagram. An axis on the bottom always shows the timeline. + */ void setType( LeveyJenningsGridAttributes::GridType type ); Qt::DateFormat dateFormat() const; void setDateFormat( Qt::DateFormat format ); /** * Returns true if both axes have the same settings. */ bool compare( const LeveyJenningsAxis* other ) const; /** reimpl */ void paintCtx( PaintContext* ) override; protected: virtual void paintAsOrdinate( PaintContext* ); virtual void paintAsAbscissa( PaintContext* ); }; typedef QList LeveyJenningsAxisList; } #endif diff --git a/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp b/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp index f6e4eaf..4e81347 100644 --- a/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp +++ b/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp @@ -1,737 +1,628 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLeveyJenningsDiagram.h" #include "KChartLeveyJenningsDiagram_p.h" #include "KChartChart.h" #include "KChartTextAttributes.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include #include #include #include #include using namespace KChart; using namespace std; LeveyJenningsDiagram::Private::Private() { } LeveyJenningsDiagram::Private::~Private() {} #define d d_func() LeveyJenningsDiagram::LeveyJenningsDiagram( QWidget* parent, LeveyJenningsCoordinatePlane* plane ) : LineDiagram( new Private(), parent, plane ) { init(); } void LeveyJenningsDiagram::init() { d->lotChangedPosition = Qt::AlignTop; d->fluidicsPackChangedPosition = Qt::AlignBottom; d->sensorChangedPosition = Qt::AlignBottom; d->scanLinePen = QPen( Qt::blue ); setPen( d->scanLinePen ); d->expectedMeanValue = 0.0; d->expectedStandardDeviation = 0.0; d->diagram = this; d->icons[ LotChanged ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/karo_black.svg" ); d->icons[ SensorChanged ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/karo_red.svg" ); d->icons[ FluidicsPackChanged ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/karo_blue.svg" ); d->icons[ OkDataPoint ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/circle_blue.svg" ); d->icons[ NotOkDataPoint ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/circle_blue_red.svg" ); setSelectionMode( QAbstractItemView::SingleSelection ); } LeveyJenningsDiagram::~LeveyJenningsDiagram() { } -/** - * Creates an exact copy of this diagram. - */ LineDiagram * LeveyJenningsDiagram::clone() const { LeveyJenningsDiagram* newDiagram = new LeveyJenningsDiagram( new Private( *d ) ); return newDiagram; } bool LeveyJenningsDiagram::compare( const LeveyJenningsDiagram* other ) const { if ( other == this ) return true; if ( ! other ) { return false; } /* qDebug() <<"\n LineDiagram::compare():"; // compare own properties qDebug() << (type() == other->type()); */ return // compare the base class ( static_cast(this)->compare( other ) ); } -/** - * Sets the position of the lot change symbol to \a pos. - * Valid values are: Qt::AlignTop (default), Qt::AlignBottom. - */ void LeveyJenningsDiagram::setLotChangedSymbolPosition( Qt::Alignment pos ) { if ( d->lotChangedPosition == pos ) return; d->lotChangedPosition = pos; update(); } -/** - * Returns the position of the lot change symbol. - */ Qt::Alignment LeveyJenningsDiagram::lotChangedSymbolPosition() const { return d->lotChangedPosition; } -/** - * Sets the position of the fluidics pack changed symbol to \a pos. - * Valid values are: Qt::AlignBottom (default), Qt::AlignTop. - */ void LeveyJenningsDiagram::setFluidicsPackChangedSymbolPosition( Qt::Alignment pos ) { if ( d->fluidicsPackChangedPosition == pos ) return; d->fluidicsPackChangedPosition = pos; update(); } -/** - * Returns the position of the fluidics pack changed symbol. - */ Qt::Alignment LeveyJenningsDiagram::fluidicsPackChangedSymbolPosition() const { return d->fluidicsPackChangedPosition; } -/** - * Sets the position of the sensor changed symbol to \a pos. - * Valid values are: Qt::AlignBottom (default), Qt::AlignTop. - */ void LeveyJenningsDiagram::setSensorChangedSymbolPosition( Qt::Alignment pos ) { if ( d->sensorChangedPosition == pos ) return; d->sensorChangedPosition = pos; update(); } -/** - * Returns the position of the sensor changed symbol. - */ Qt::Alignment LeveyJenningsDiagram::sensorChangedSymbolPosition() const { return d->sensorChangedPosition; } -/** - * Sets the date/time of all fluidics pack changes to \a changes. - */ void LeveyJenningsDiagram::setFluidicsPackChanges( const QVector< QDateTime >& changes ) { if ( d->fluidicsPackChanges == changes ) return; d->fluidicsPackChanges = changes; update(); } -/** - * Returns the list of all fluidics pack changes. - */ QVector< QDateTime > LeveyJenningsDiagram::fluidicsPackChanges() const { return d->fluidicsPackChanges; } -/** - * Sets the date/time of all sensor changes to \a changes. - */ void LeveyJenningsDiagram::setSensorChanges( const QVector< QDateTime >& changes ) { if ( d->sensorChanges == changes ) return; d->sensorChanges = changes; update(); } -/** - * Sets the pen used for drawing the scan line to \a pen - */ void LeveyJenningsDiagram::setScanLinePen( const QPen& pen ) { if ( d->scanLinePen == pen ) return; d->scanLinePen = pen; update(); } -/** - * Returns the pen being used for drawing the scan line. - */ QPen LeveyJenningsDiagram::scanLinePen() const { return d->scanLinePen; } -/** - * Returns the SVG file name usef for \a symbol - */ QString LeveyJenningsDiagram::symbol( Symbol symbol ) const { return d->icons[ symbol ]; } -/** - * Sets the symbol being used for \a symbol to a SVG file \a filename. - */ void LeveyJenningsDiagram::setSymbol( Symbol symbol, const QString& filename ) { if ( d->icons[ symbol ] == filename ) return; delete d->iconRenderer[ symbol ]; d->iconRenderer[ symbol ] = nullptr; d->icons[ symbol ] = filename; update(); } -/** - * Returns the list of all sensor changes. - */ QVector< QDateTime > LeveyJenningsDiagram::sensorChanges() const { return d->sensorChanges; } -/** - * Sets the expected mean value over all QC values to \a meanValue. - */ void LeveyJenningsDiagram::setExpectedMeanValue( float meanValue ) { if ( d->expectedMeanValue == meanValue ) return; d->expectedMeanValue = meanValue; d->setYAxisRange(); update(); } -/** - * Returns the expected mean values over all QC values. - */ float LeveyJenningsDiagram::expectedMeanValue() const { return d->expectedMeanValue; } -/** - * Sets the expected standard deviaction over all QC values to \a sd. - */ void LeveyJenningsDiagram::setExpectedStandardDeviation( float sd ) { if ( d->expectedStandardDeviation == sd ) return; d->expectedStandardDeviation = sd; d->setYAxisRange(); update(); } -/** - * Returns the expected standard deviation over all QC values. - */ float LeveyJenningsDiagram::expectedStandardDeviation() const { return d->expectedStandardDeviation; } -/** - * Returns the calculated mean values over all QC values. - */ float LeveyJenningsDiagram::calculatedMeanValue() const { return d->calculatedMeanValue; } -/** - * Returns the calculated standard deviation over all QC values. - */ float LeveyJenningsDiagram::calculatedStandardDeviation() const { return d->calculatedStandardDeviation; } void LeveyJenningsDiagram::setModel( QAbstractItemModel* model ) { if ( this->model() != nullptr ) { disconnect( this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(calculateMeanAndStandardDeviation()) ); disconnect( this->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); disconnect( this->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); disconnect( this->model(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); disconnect( this->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); disconnect( this->model(), SIGNAL(modelReset()), this, SLOT(calculateMeanAndStandardDeviation()) ); disconnect( this->model(), SIGNAL(layoutChanged()), this, SLOT(calculateMeanAndStandardDeviation()) ); } LineDiagram::setModel( model ); if ( this->model() != nullptr ) { connect( this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(calculateMeanAndStandardDeviation()) ); connect( this->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); connect( this->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); connect( this->model(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); connect( this->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(calculateMeanAndStandardDeviation()) ); connect( this->model(), SIGNAL(modelReset()), this, SLOT(calculateMeanAndStandardDeviation()) ); connect( this->model(), SIGNAL(layoutChanged()), this, SLOT(calculateMeanAndStandardDeviation()) ); calculateMeanAndStandardDeviation(); } } // TODO: This is the 'easy' solution // evaluate whether this is enough or we need some better one or even boost here void LeveyJenningsDiagram::calculateMeanAndStandardDeviation() const { QVector< qreal > values; // first fetch all values const QAbstractItemModel& m = *model(); const int rowCount = m.rowCount( rootIndex() ); for ( int row = 0; row < rowCount; ++row ) { const QVariant var = m.data( m.index( row, 1, rootIndex() ) ); if ( !var.isValid() ) continue; const qreal value = var.toReal(); if ( ISNAN( value ) ) continue; values << value; } qreal sum = 0.0; qreal sumSquares = 0.0; Q_FOREACH( qreal value, values ) { sum += value; sumSquares += value * value; } const int N = values.count(); d->calculatedMeanValue = sum / N; d->calculatedStandardDeviation = sqrt( ( static_cast< qreal >( N ) * sumSquares - sum * sum ) / ( N * ( N - 1 ) ) ); } // calculates the largest QDate not greater than \a dt. static QDate floorDay( const QDateTime& dt ) { return dt.date(); } // calculates the smallest QDate not less than \a dt. static QDate ceilDay( const QDateTime& dt ) { QDate result = dt.date(); if ( QDateTime( result, QTime() ) < dt ) result = result.addDays( 1 ); return result; } // calculates the largest QDateTime like xx:00 not greater than \a dt. static QDateTime floorHour( const QDateTime& dt ) { return QDateTime( dt.date(), QTime( dt.time().hour(), 0 ) ); } // calculates the smallest QDateTime like xx:00 not less than \a dt. static QDateTime ceilHour( const QDateTime& dt ) { QDateTime result( dt.date(), QTime( dt.time().hour(), 0 ) ); if ( result < dt ) result = result.addSecs( 3600 ); return result; } -/** \reimpl */ const QPair LeveyJenningsDiagram::calculateDataBoundaries() const { const qreal yMin = d->expectedMeanValue - 4 * d->expectedStandardDeviation; const qreal yMax = d->expectedMeanValue + 4 * d->expectedStandardDeviation; d->setYAxisRange(); // rounded down/up to the prev/next midnight (at least that's the default) const QPair< QDateTime, QDateTime > range = timeRange(); const unsigned int minTime = range.first.toSecsSinceEpoch(); const unsigned int maxTime = range.second.toSecsSinceEpoch(); const qreal xMin = minTime / static_cast< qreal >( 24 * 60 * 60 ); const qreal xMax = maxTime / static_cast< qreal >( 24 * 60 * 60 ) - xMin; const QPointF bottomLeft( QPointF( 0, yMin ) ); const QPointF topRight( QPointF( xMax, yMax ) ); return QPair< QPointF, QPointF >( bottomLeft, topRight ); } -/** - * Returns the timerange of the diagram's data. - */ QPair< QDateTime, QDateTime > LeveyJenningsDiagram::timeRange() const { if ( d->timeRange != QPair< QDateTime, QDateTime >() ) return d->timeRange; const QAbstractItemModel& m = *model(); const int rowCount = m.rowCount( rootIndex() ); const QDateTime begin = m.data( m.index( 0, 3, rootIndex() ) ).toDateTime(); const QDateTime end = m.data( m.index( rowCount - 1, 3, rootIndex() ) ).toDateTime(); if ( begin.secsTo( end ) > 86400 ) { // if begin to end is more than 24h // round down/up to the prev/next midnight const QDate min = floorDay( begin ); const QDate max = ceilDay( end ); return QPair< QDateTime, QDateTime >( QDateTime( min ), QDateTime( max ) ); } else if ( begin.secsTo( end ) > 3600 ) { // more than 1h: rond down up to the prex/next hour // if begin to end is more than 24h const QDateTime min = floorHour( begin ); const QDateTime max = ceilHour( end ); return QPair< QDateTime, QDateTime >( min, max ); } return QPair< QDateTime, QDateTime >( begin, end ); } -/** - * Sets the \a timeRange visible on the x axis. Set it to QPair< QDateTime, QDateTime >() - * to use the default auto calculation. - */ void LeveyJenningsDiagram::setTimeRange( const QPair< QDateTime, QDateTime >& timeRange ) { if ( d->timeRange == timeRange ) return; d->timeRange = timeRange; update(); } -/** - * Draws the fluidics pack and sensor changed symbols. - */ void LeveyJenningsDiagram::drawChanges( PaintContext* ctx ) { const unsigned int minTime = timeRange().first.toSecsSinceEpoch(); Q_FOREACH( const QDateTime& dt, d->fluidicsPackChanges ) { const qreal xValue = ( dt.toSecsSinceEpoch() - minTime ) / static_cast< qreal >( 24 * 60 * 60 ); const QPointF point( xValue, 0.0 ); drawFluidicsPackChangedSymbol( ctx, point ); } Q_FOREACH( const QDateTime& dt, d->sensorChanges ) { const qreal xValue = ( dt.toSecsSinceEpoch() - minTime ) / static_cast< qreal >( 24 * 60 * 60 ); const QPointF point( xValue, 0.0 ); drawSensorChangedSymbol( ctx, point ); } } -/** \reimpl */ void LeveyJenningsDiagram::paint( PaintContext* ctx ) { d->reverseMapper.clear(); // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants( true ) ) return; if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return; QPainter* const painter = ctx->painter(); const PainterSaver p( painter ); if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) < 4 ) return; // nothing to paint for us AbstractCoordinatePlane* const plane = ctx->coordinatePlane(); ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( painter ) ); const QAbstractItemModel& m = *model(); const int rowCount = m.rowCount( rootIndex() ); const unsigned int minTime = timeRange().first.toSecsSinceEpoch(); painter->setRenderHint( QPainter::Antialiasing, true ); int prevLot = -1; QPointF prevPoint; bool hadMissingValue = false; for ( int row = 0; row < rowCount; ++row ) { const QModelIndex lotIndex = m.index( row, 0, rootIndex() ); const QModelIndex valueIndex = m.index( row, 1, rootIndex() ); const QModelIndex okIndex = m.index( row, 2, rootIndex() ); const QModelIndex timeIndex = m.index( row, 3, rootIndex() ); const QModelIndex expectedMeanIndex = m.index( row, 4, rootIndex() ); const QModelIndex expectedSDIndex = m.index( row, 5, rootIndex() ); painter->setPen( pen( lotIndex ) ); QVariant vValue = m.data( valueIndex ); qreal value = vValue.toReal(); const int lot = m.data( lotIndex ).toInt(); const bool ok = m.data( okIndex ).toBool(); const QDateTime time = m.data( timeIndex ).toDateTime(); const qreal xValue = ( time.toSecsSinceEpoch() - minTime ) / static_cast< qreal >( 24 * 60 * 60 ); QVariant vExpectedMean = m.data( expectedMeanIndex ); const qreal expectedMean = vExpectedMean.toReal(); QVariant vExpectedSD = m.data( expectedSDIndex ); const qreal expectedSD = vExpectedSD.toReal(); QPointF point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) ); if ( vValue.isNull() ) { hadMissingValue = true; } else { if ( !vExpectedMean.isNull() && !vExpectedSD.isNull() ) { // this calculates the 'logical' value relative to the expected mean and SD of this point value -= expectedMean; value /= expectedSD; value *= d->expectedStandardDeviation; value += d->expectedMeanValue; point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) ); } if ( prevLot == lot ) { const QPen pen = painter->pen(); QPen newPen = pen; if ( hadMissingValue ) { newPen.setDashPattern( QVector< qreal >() << 4.0 << 4.0 ); } painter->setPen( newPen ); painter->drawLine( prevPoint, point ); painter->setPen( pen ); // d->reverseMapper.addLine( valueIndex.row(), valueIndex.column(), prevPoint, point ); } else if ( row > 0 ) { drawLotChangeSymbol( ctx, QPointF( xValue, value ) ); } if ( value <= d->expectedMeanValue + 4 * d->expectedStandardDeviation && value >= d->expectedMeanValue - 4 * d->expectedStandardDeviation ) { const QPointF location( xValue, value ); drawDataPointSymbol( ctx, location, ok ); d->reverseMapper.addCircle( valueIndex.row(), valueIndex.column(), ctx->coordinatePlane()->translate( location ), iconRect().size() ); } prevLot = lot; prevPoint = point; hadMissingValue = false; } const QModelIndex current = selectionModel()->currentIndex(); if ( selectionModel()->rowIntersectsSelection( lotIndex.row(), lotIndex.parent() ) || current.sibling( current.row(), 0 ) == lotIndex ) { const QPen pen = ctx->painter()->pen(); painter->setPen( d->scanLinePen ); painter->drawLine( ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ), ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue + 4 * d->expectedStandardDeviation ) ) ); painter->setPen( pen ); } } drawChanges( ctx ); ctx->setCoordinatePlane( plane ); } -/** - * Draws a data point symbol for the data point at \a pos. - * @param ok True, when the data point is ok, false otherwise (different symbol) - * @param ctx The PaintContext being used - * @param pos Position - * @param ok Draw as Ok or notOK data point - */ void LeveyJenningsDiagram::drawDataPointSymbol( PaintContext* ctx, const QPointF& pos, bool ok ) { const Symbol type = ok ? OkDataPoint : NotOkDataPoint; QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); const QPointF transPos = ctx->coordinatePlane()->translate( pos ).toPoint(); painter->translate( transPos ); painter->setClipping( false ); iconRenderer( type )->render( painter, iconRect() ); } -/** - * Draws a lot changed symbol for the data point at \a pos. - * @param ctx The PaintContext being used - * @param pos Position - * \sa lotChangedSymbolPosition - */ void LeveyJenningsDiagram::drawLotChangeSymbol( PaintContext* ctx, const QPointF& pos ) { const QPointF transPos = ctx->coordinatePlane()->translate( QPointF( pos.x(), d->lotChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ); QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); painter->setClipping( false ); painter->translate( transPos ); iconRenderer( LotChanged )->render( painter, iconRect() ); } -/** - * Draws a sensor changed symbol for the data point at \a pos. - * @param ctx The PaintContext being used - * @param pos Position - * \sa sensorChangedSymbolPosition - */ void LeveyJenningsDiagram::drawSensorChangedSymbol( PaintContext* ctx, const QPointF& pos ) { const QPointF transPos = ctx->coordinatePlane()->translate( QPointF( pos.x(), d->sensorChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ); QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); painter->setClipping( false ); painter->translate( transPos ); iconRenderer( SensorChanged )->render( painter, iconRect() ); } -/** - * Draws a fluidics pack changed symbol for the data point at \a pos. - * @param ctx The PaintContext being used - * @param pos Position - * \sa fluidicsPackChangedSymbolPosition - */ void LeveyJenningsDiagram::drawFluidicsPackChangedSymbol( PaintContext* ctx, const QPointF& pos ) { const QPointF transPos = ctx->coordinatePlane()->translate( QPointF( pos.x(), d->fluidicsPackChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ); QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); painter->setClipping( false ); painter->translate( transPos ); iconRenderer( FluidicsPackChanged )->render( painter, iconRect() ); } -/** - * Returns the rectangle being used for drawing the icons - */ QRectF LeveyJenningsDiagram::iconRect() const { const Measure m( 12.5, KChartEnums::MeasureCalculationModeAuto, KChartEnums::MeasureOrientationAuto ); TextAttributes test; test.setFontSize( m ); const QFontMetrics fm( test.calculatedFont( coordinatePlane()->parent(), KChartEnums::MeasureOrientationAuto ) ); const qreal height = fm.height() / 1.2; return QRectF( -height / 2.0, -height / 2.0, height, height ); } -/** - * Returns the SVG icon renderer for \a symbol - */ QSvgRenderer* LeveyJenningsDiagram::iconRenderer( Symbol symbol ) { if ( d->iconRenderer[ symbol ] == nullptr ) d->iconRenderer[ symbol ] = new QSvgRenderer( d->icons[ symbol ], this ); return d->iconRenderer[ symbol ]; } diff --git a/src/KChart/Cartesian/KChartLeveyJenningsDiagram.h b/src/KChart/Cartesian/KChartLeveyJenningsDiagram.h index e9ee3ea..89254f9 100644 --- a/src/KChart/Cartesian/KChartLeveyJenningsDiagram.h +++ b/src/KChart/Cartesian/KChartLeveyJenningsDiagram.h @@ -1,129 +1,270 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTLEVEYJENNINGSDIAGRAM_H #define KCHARTLEVEYJENNINGSDIAGRAM_H #include "KChartLineDiagram.h" #include "KChartLeveyJenningsCoordinatePlane.h" QT_BEGIN_NAMESPACE class QPainter; class QPolygonF; class QSvgRenderer; QT_END_NAMESPACE namespace KChart { class ThreeDLineAttributes; /** * @brief LeveyDiagram defines a Levey Jennings chart. * * It provides different subtypes which are set using \a setType. */ class KCHART_EXPORT LeveyJenningsDiagram : public LineDiagram { Q_OBJECT Q_DISABLE_COPY( LeveyJenningsDiagram ) // KCHART_DECLARE_PRIVATE_DERIVED_PARENT( LineDiagram, CartesianCoordinatePlane * ) KCHART_DECLARE_DERIVED_DIAGRAM( LeveyJenningsDiagram, LeveyJenningsCoordinatePlane ) public: explicit LeveyJenningsDiagram( QWidget* parent = nullptr, LeveyJenningsCoordinatePlane* plane = nullptr ); virtual ~LeveyJenningsDiagram(); - LineDiagram * clone() const override; + + /** + * Creates an exact copy of this diagram. + */ + LineDiagram * clone() const override; enum Symbol { OkDataPoint, NotOkDataPoint, LotChanged, SensorChanged, FluidicsPackChanged }; /** * Returns true if both diagrams have the same settings. */ bool compare( const LeveyJenningsDiagram* other ) const; + + /** + * Sets the position of the lot change symbol to \a pos. + * Valid values are: Qt::AlignTop (default), Qt::AlignBottom. + */ void setLotChangedSymbolPosition( Qt::Alignment pos ); + + /** + * Returns the position of the lot change symbol. + */ Qt::Alignment lotChangedSymbolPosition() const; + + /** + * Sets the position of the fluidics pack changed symbol to \a pos. + * Valid values are: Qt::AlignBottom (default), Qt::AlignTop. + */ void setFluidicsPackChangedSymbolPosition( Qt::Alignment pos ); + + /** + * Returns the position of the fluidics pack changed symbol. + */ Qt::Alignment fluidicsPackChangedSymbolPosition() const; + + /** + * Sets the position of the sensor changed symbol to \a pos. + * Valid values are: Qt::AlignBottom (default), Qt::AlignTop. + */ void setSensorChangedSymbolPosition( Qt::Alignment pos ); + + /** + * Returns the position of the sensor changed symbol. + */ Qt::Alignment sensorChangedSymbolPosition() const; + + /** + * Sets the expected mean value over all QC values to \a meanValue. + */ void setExpectedMeanValue( float meanValue ); + + /** + * Returns the expected mean values over all QC values. + */ float expectedMeanValue() const; + + /** + * Sets the expected standard deviaction over all QC values to \a sd. + */ void setExpectedStandardDeviation( float sd ); + + /** + * Returns the expected standard deviation over all QC values. + */ float expectedStandardDeviation() const; + + /** + * Returns the calculated mean values over all QC values. + */ float calculatedMeanValue() const; + + /** + * Returns the calculated standard deviation over all QC values. + */ float calculatedStandardDeviation() const; + + /** + * Sets the date/time of all fluidics pack changes to \a changes. + */ void setFluidicsPackChanges( const QVector< QDateTime >& changes ); + + /** + * Returns the list of all fluidics pack changes. + */ QVector< QDateTime > fluidicsPackChanges() const; + + /** + * Sets the date/time of all sensor changes to \a changes. + */ void setSensorChanges( const QVector< QDateTime >& changes ); + + /** + * Returns the list of all sensor changes. + */ QVector< QDateTime > sensorChanges() const; + + /** + * Sets the pen used for drawing the scan line to \a pen + */ void setScanLinePen( const QPen& pen ); + + /** + * Returns the pen being used for drawing the scan line. + */ QPen scanLinePen() const; + + /** + * Sets the symbol being used for \a symbol to a SVG file \a filename. + */ void setSymbol( Symbol symbol, const QString& filename ); + + /** + * Returns the SVG file name usef for \a symbol + */ QString symbol( Symbol symbol ) const; /* \reimpl */ void setModel( QAbstractItemModel* model ) override; + + /** + * Returns the timerange of the diagram's data. + */ QPair< QDateTime, QDateTime > timeRange() const; + + /** + * Sets the \a timeRange visible on the x axis. Set it to QPair< QDateTime, QDateTime >() + * to use the default auto calculation. + */ void setTimeRange( const QPair< QDateTime, QDateTime >& timeRange ); protected: + + /** \reimpl */ void paint( PaintContext* paintContext ) override; + + /** + * Draws the fluidics pack and sensor changed symbols. + */ void drawChanges( PaintContext* paintContext ); + + /** + * Draws a data point symbol for the data point at \a pos. + * @param ok True, when the data point is ok, false otherwise (different symbol) + * @param ctx The PaintContext being used + * @param pos Position + * @param ok Draw as Ok or notOK data point + */ virtual void drawDataPointSymbol( PaintContext* paintContext, const QPointF& pos, bool ok ); + + /** + * Draws a lot changed symbol for the data point at \a pos. + * @param ctx The PaintContext being used + * @param pos Position + * \sa lotChangedSymbolPosition + */ virtual void drawLotChangeSymbol( PaintContext* paintContext, const QPointF& pos ); + + /** + * Draws a sensor changed symbol for the data point at \a pos. + * @param ctx The PaintContext being used + * @param pos Position + * \sa sensorChangedSymbolPosition + */ virtual void drawSensorChangedSymbol( PaintContext* paintContext, const QPointF& pos ); + + /** + * Draws a fluidics pack changed symbol for the data point at \a pos. + * @param ctx The PaintContext being used + * @param pos Position + * \sa fluidicsPackChangedSymbolPosition + */ virtual void drawFluidicsPackChangedSymbol( PaintContext* paintContext, const QPointF& pos ); + + /** + * Returns the rectangle being used for drawing the icons + */ virtual QRectF iconRect() const; + + /** + * Returns the SVG icon renderer for \a symbol + */ QSvgRenderer* iconRenderer( Symbol symbol ); /* \reimpl */ + + /** \reimpl */ const QPair calculateDataBoundaries() const override; protected Q_SLOTS: void calculateMeanAndStandardDeviation() const; }; // End of class KChartLineDiagram } #endif // KCHARTLINEDIAGRAM_H diff --git a/src/KChart/Cartesian/KChartLineDiagram.cpp b/src/KChart/Cartesian/KChartLineDiagram.cpp index d8ab874..a477093 100644 --- a/src/KChart/Cartesian/KChartLineDiagram.cpp +++ b/src/KChart/Cartesian/KChartLineDiagram.cpp @@ -1,433 +1,375 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLineDiagram.h" #include "KChartLineDiagram_p.h" #include "KChartBarDiagram.h" #include "KChartPalette.h" #include "KChartAttributesModel.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include "KChartNormalLineDiagram_p.h" #include "KChartStackedLineDiagram_p.h" #include "KChartPercentLineDiagram_p.h" #include "KChartMath_p.h" #include #include #include #include #include #include using namespace KChart; LineDiagram::Private::Private() { } LineDiagram::Private::~Private() {} #define d d_func() LineDiagram::LineDiagram( QWidget* parent, CartesianCoordinatePlane* plane ) : AbstractCartesianDiagram( new Private(), parent, plane ) { init(); } void LineDiagram::init() { d->normalDiagram = new NormalLineDiagram( this ); d->stackedDiagram = new StackedLineDiagram( this ); d->percentDiagram = new PercentLineDiagram( this ); d->implementor = d->normalDiagram; d->centerDataPoints = false; d->reverseDatasetOrder = false; } LineDiagram::~LineDiagram() { delete d->normalDiagram; delete d->stackedDiagram; delete d->percentDiagram; } -/** - * Creates an exact copy of this diagram. - */ LineDiagram * LineDiagram::clone() const { LineDiagram* newDiagram = new LineDiagram( new Private( *d ) ); newDiagram->setType( type() ); return newDiagram; } bool LineDiagram::compare( const LineDiagram* other ) const { if ( other == this ) return true; if ( ! other ) { return false; } return // compare the base class ( static_cast(this)->compare( other ) ) && // compare own properties (type() == other->type()) && (centerDataPoints() == other->centerDataPoints()) && (reverseDatasetOrder() == other->reverseDatasetOrder()); } -/** - * Sets the line diagram's type to \a type - * \sa LineDiagram::LineType - */ void LineDiagram::setType( const LineType type ) { if ( d->implementor->type() == type ) return; if ( type != LineDiagram::Normal && datasetDimension() > 1 ) { Q_ASSERT_X ( false, "setType()", "This line chart type can't be used with multi-dimensional data." ); return; } switch ( type ) { case Normal: d->implementor = d->normalDiagram; break; case Stacked: d->implementor = d->stackedDiagram; break; case Percent: d->implementor = d->percentDiagram; break; default: Q_ASSERT_X( false, "LineDiagram::setType", "unknown diagram subtype" ); }; // d->lineType = type; Q_ASSERT( d->implementor->type() == type ); // AbstractAxis settings - see AbstractDiagram and CartesianAxis setPercentMode( type == LineDiagram::Percent ); setDataBoundariesDirty(); emit layoutChanged( this ); emit propertiesChanged(); } -/** - * @return the type of the line diagram - */ LineDiagram::LineType LineDiagram::type() const { return d->implementor->type(); } void LineDiagram::setCenterDataPoints( bool center ) { if ( d->centerDataPoints == center ) { return; } d->centerDataPoints = center; // The actual data boundaries haven't changed, but the axis will have one more or less tick // A B =\ A B // 1......2 =/ 1......2......3 setDataBoundariesDirty(); emit layoutChanged( this ); emit propertiesChanged(); } bool LineDiagram::centerDataPoints() const { return d->centerDataPoints; } void LineDiagram::setReverseDatasetOrder( bool reverse ) { d->reverseDatasetOrder = reverse; } bool LineDiagram::reverseDatasetOrder() const { return d->reverseDatasetOrder; } -/** - * Sets the global line attributes to \a la - */ void LineDiagram::setLineAttributes( const LineAttributes& la ) { d->attributesModel->setModelData( QVariant::fromValue( la ), LineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the line attributes of data set \a column to \a la - */ void LineDiagram::setLineAttributes( int column, const LineAttributes& la ) { d->setDatasetAttrs( column, QVariant::fromValue( la ), LineAttributesRole ); emit propertiesChanged(); } -/** - * Resets the line attributes of data set \a column - */ void LineDiagram::resetLineAttributes( int column ) { d->resetDatasetAttrs( column, LineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the line attributes for the model index \a index to \a la - */ void LineDiagram::setLineAttributes( const QModelIndex& index, const LineAttributes& la ) { d->attributesModel->setData( d->attributesModel->mapFromSource(index), QVariant::fromValue( la ), LineAttributesRole ); emit propertiesChanged(); } -/** - * Remove any explicit line attributes settings that might have been specified before. - */ void LineDiagram::resetLineAttributes( const QModelIndex & index ) { d->attributesModel->resetData( d->attributesModel->mapFromSource(index), LineAttributesRole ); emit propertiesChanged(); } -/** - * @return the global line attribute set - */ LineAttributes LineDiagram::lineAttributes() const { return d->attributesModel->data( KChart::LineAttributesRole ).value(); } -/** - * @return the line attribute set of data set \a column - */ LineAttributes LineDiagram::lineAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, LineAttributesRole ) ); if ( attrs.isValid() ) return attrs.value(); return lineAttributes(); } -/** - * @return the line attribute set of the model index \a index - */ LineAttributes LineDiagram::lineAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource(index), KChart::LineAttributesRole ).value(); } -/** - * Sets the global 3D line attributes to \a la - */ void LineDiagram::setThreeDLineAttributes( const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->attributesModel->setModelData( QVariant::fromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the 3D line attributes of data set \a column to \a ta - */ void LineDiagram::setThreeDLineAttributes( int column, const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->setDatasetAttrs( column, QVariant::fromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the 3D line attributes of model index \a index to \a la - */ void LineDiagram::setThreeDLineAttributes( const QModelIndex & index, const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->attributesModel->setData( d->attributesModel->mapFromSource(index), QVariant::fromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } -/** - * @return the global 3D line attributes - */ ThreeDLineAttributes LineDiagram::threeDLineAttributes() const { return d->attributesModel->data( KChart::ThreeDLineAttributesRole ).value(); } -/** - * @return the 3D line attributes of data set \a column - */ ThreeDLineAttributes LineDiagram::threeDLineAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, ThreeDLineAttributesRole ) ); if ( attrs.isValid() ) return attrs.value(); return threeDLineAttributes(); } -/** - * @return the 3D line attributes of the model index \a index - */ ThreeDLineAttributes LineDiagram::threeDLineAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::ThreeDLineAttributesRole ).value(); } qreal LineDiagram::threeDItemDepth( const QModelIndex& index ) const { return threeDLineAttributes( index ).validDepth(); } qreal LineDiagram::threeDItemDepth( int column ) const { return threeDLineAttributes( column ).validDepth(); } -/** - * Sets the value tracker attributes of the model index \a index to \a va - */ void LineDiagram::setValueTrackerAttributes( const QModelIndex & index, const ValueTrackerAttributes & va ) { d->attributesModel->setData( d->attributesModel->mapFromSource(index), QVariant::fromValue( va ), KChart::ValueTrackerAttributesRole ); emit propertiesChanged(); } -/** - * Returns the value tracker attributes of the model index \a index - */ ValueTrackerAttributes LineDiagram::valueTrackerAttributes( const QModelIndex & index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::ValueTrackerAttributesRole ).value(); } void LineDiagram::resizeEvent ( QResizeEvent* ) { } const QPair LineDiagram::calculateDataBoundaries() const { d->compressor.setResolution( static_cast( this->size().width() * coordinatePlane()->zoomFactorX() ), static_cast( this->size().height() * coordinatePlane()->zoomFactorY() ) ); if ( !checkInvariants( true ) ) return QPair( QPointF( 0, 0 ), QPointF( 0, 0 ) ); // note: calculateDataBoundaries() is ignoring the hidden flags. // That's not a bug but a feature: Hiding data does not mean removing them. // For totally removing data from KD Chart's view people can use e.g. a proxy model ... // calculate boundaries for different line types Normal - Stacked - Percent - Default Normal return d->implementor->calculateDataBoundaries(); } void LineDiagram::paintEvent ( QPaintEvent*) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle ( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void LineDiagram::paint( PaintContext* ctx ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants( true ) ) return; if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return; const PainterSaver p( ctx->painter() ); if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) == 0 ) return; // nothing to paint for us AbstractCoordinatePlane* const plane = ctx->coordinatePlane(); ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( ctx->painter() ) ); // paint different line types Normal - Stacked - Percent - Default Normal d->implementor->paint( ctx ); ctx->setCoordinatePlane( plane ); } void LineDiagram::resize ( const QSizeF& size ) { d->compressor.setResolution( static_cast( size.width() * coordinatePlane()->zoomFactorX() ), static_cast( size.height() * coordinatePlane()->zoomFactorY() ) ); setDataBoundariesDirty(); AbstractCartesianDiagram::resize( size ); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int LineDiagram::numberOfAbscissaSegments () const { return d->attributesModel->rowCount(attributesModelRootIndex()); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int LineDiagram::numberOfOrdinateSegments () const { return d->attributesModel->columnCount(attributesModelRootIndex()); } diff --git a/src/KChart/Cartesian/KChartLineDiagram.h b/src/KChart/Cartesian/KChartLineDiagram.h index 92b9d68..31ce821 100644 --- a/src/KChart/Cartesian/KChartLineDiagram.h +++ b/src/KChart/Cartesian/KChartLineDiagram.h @@ -1,148 +1,225 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTLINEDIAGRAM_H #define KCHARTLINEDIAGRAM_H #include "KChartAbstractCartesianDiagram.h" #include "KChartLineAttributes.h" #include "KChartValueTrackerAttributes.h" QT_BEGIN_NAMESPACE class QPainter; class QPolygonF; QT_END_NAMESPACE namespace KChart { class ThreeDLineAttributes; /** * @brief LineDiagram defines a common line diagram. * * It provides different subtypes which are set using \a setType. */ class KCHART_EXPORT LineDiagram : public AbstractCartesianDiagram { Q_OBJECT Q_DISABLE_COPY( LineDiagram ) // KCHART_DECLARE_PRIVATE_DERIVED_PARENT( LineDiagram, CartesianCoordinatePlane * ) KCHART_DECLARE_DERIVED_DIAGRAM( LineDiagram, CartesianCoordinatePlane ) public: class LineDiagramType; friend class LineDiagramType; explicit LineDiagram( QWidget* parent = nullptr, CartesianCoordinatePlane* plane = nullptr ); virtual ~LineDiagram(); - virtual LineDiagram * clone() const; + + /** + * Creates an exact copy of this diagram. + */ + virtual LineDiagram * clone() const; /** * Returns true if both diagrams have the same settings. */ bool compare( const LineDiagram* other ) const; enum LineType { Normal = 0, Stacked = 1, Percent = 2 }; + + /** + * Sets the line diagram's type to \a type + * \sa LineDiagram::LineType + */ void setType( const LineType type ); + + /** + * @return the type of the line diagram + */ LineType type() const; /** If centerDataPoints() is true, all data points are moved by an * offset of 0.5 to the right. This is useful in conjunction with * bar diagrams, since data points are then centered just like bars. * * \sa centerDataPoints() */ void setCenterDataPoints( bool center ); /** @return option set by setCenterDataPoints() */ bool centerDataPoints() const; /** With this property set to true, data sets in a normal line diagram * are drawn in reversed order. More clearly, the first (top-most) data set * in the source model will then appear in front. This is mostly due to * historical reasons. */ void setReverseDatasetOrder( bool reverse ); /** \see setReverseDatasetOrder */ bool reverseDatasetOrder() const; + + /** + * Sets the global line attributes to \a la + */ void setLineAttributes( const LineAttributes & a ); + + /** + * Sets the line attributes of data set \a column to \a la + */ void setLineAttributes( int column, const LineAttributes & a ); + + /** + * Sets the line attributes for the model index \a index to \a la + */ void setLineAttributes( const QModelIndex & index, const LineAttributes & a ); + + /** + * Resets the line attributes of data set \a column + */ void resetLineAttributes( int column ); + + /** + * Remove any explicit line attributes settings that might have been specified before. + */ void resetLineAttributes( const QModelIndex & index ); + + /** + * @return the global line attribute set + */ LineAttributes lineAttributes() const; + + /** + * @return the line attribute set of data set \a column + */ LineAttributes lineAttributes( int column ) const; + + /** + * @return the line attribute set of the model index \a index + */ LineAttributes lineAttributes( const QModelIndex & index ) const; + + /** + * Sets the global 3D line attributes to \a la + */ void setThreeDLineAttributes( const ThreeDLineAttributes & a ); - void setThreeDLineAttributes( int column, const ThreeDLineAttributes & a ); + + /** + * Sets the 3D line attributes of data set \a column to \a ta + */ + void setThreeDLineAttributes( int column, const ThreeDLineAttributes &ta ); + + /** + * Sets the 3D line attributes of model index \a index to \a la + */ void setThreeDLineAttributes( const QModelIndex & index, - const ThreeDLineAttributes & a ); + const ThreeDLineAttributes &la ); + + /** + * @return the global 3D line attributes + */ ThreeDLineAttributes threeDLineAttributes() const; + + /** + * @return the 3D line attributes of data set \a column + */ ThreeDLineAttributes threeDLineAttributes( int column ) const; + + /** + * @return the 3D line attributes of the model index \a index + */ ThreeDLineAttributes threeDLineAttributes( const QModelIndex & index ) const; - void setValueTrackerAttributes( const QModelIndex & index, + + /** + * Sets the value tracker attributes of the model index \a index to \a va + */ + void setValueTrackerAttributes( const QModelIndex & index, const ValueTrackerAttributes & a ); - ValueTrackerAttributes valueTrackerAttributes( const QModelIndex & index ) const; + + /** + * Returns the value tracker attributes of the model index \a index + */ + ValueTrackerAttributes valueTrackerAttributes( const QModelIndex & index ) const; #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) // implement AbstractCartesianDiagram /* reimpl */ const int numberOfAbscissaSegments () const; /* reimpl */ const int numberOfOrdinateSegments () const; #else // implement AbstractCartesianDiagram /* reimpl */ int numberOfAbscissaSegments () const override; /* reimpl */ int numberOfOrdinateSegments () const override; #endif protected: void paint ( PaintContext* paintContext ) override; public: void resize ( const QSizeF& area ) override; protected: qreal threeDItemDepth( const QModelIndex & index ) const override; qreal threeDItemDepth( int column ) const override; /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent ( QPaintEvent* ) override; void resizeEvent ( QResizeEvent* ) override; }; // End of class KChartLineDiagram } #endif // KCHARTLINEDIAGRAM_H diff --git a/src/KChart/Cartesian/KChartPlotter.cpp b/src/KChart/Cartesian/KChartPlotter.cpp index edf8df3..0248d23 100644 --- a/src/KChart/Cartesian/KChartPlotter.cpp +++ b/src/KChart/Cartesian/KChartPlotter.cpp @@ -1,488 +1,431 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartPlotter.h" #include "KChartPlotter_p.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include "KChartMath_p.h" #include "KChartNormalPlotter_p.h" #include "KChartPercentPlotter_p.h" #include "KChartStackedPlotter_p.h" using namespace KChart; Plotter::Private::Private() : implementor( nullptr ) , normalPlotter( nullptr ) , percentPlotter( nullptr ) , stackedPlotter( nullptr ) { } Plotter::Private::~Private() { delete normalPlotter; delete percentPlotter; delete stackedPlotter; } #define d d_func() Plotter::Plotter( QWidget* parent, CartesianCoordinatePlane* plane ) : AbstractCartesianDiagram( new Private(), parent, plane ) { init(); } void Plotter::init() { d->diagram = this; d->normalPlotter = new NormalPlotter( this ); d->percentPlotter = new PercentPlotter( this ); d->stackedPlotter = new StackedPlotter( this ); d->implementor = d->normalPlotter; QObject* test = d->implementor->plotterPrivate(); connect( this, SIGNAL(boundariesChanged()), test, SLOT(changedProperties()) ); // The signal is connected to the superclass's slot at this point because the connection happened // in its constructor when "its type was not Plotter yet". disconnect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)), this, SLOT(connectAttributesModel(AttributesModel*)) ); connect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)), this, SLOT(connectAttributesModel(AttributesModel*)) ); setDatasetDimensionInternal( 2 ); } Plotter::~Plotter() { } -/** - * Creates an exact copy of this diagram. - */ Plotter* Plotter::clone() const { Plotter* newDiagram = new Plotter( new Private( *d ) ); newDiagram->setType( type() ); return newDiagram; } bool Plotter::compare( const Plotter* other ) const { if ( other == this ) return true; if ( other == nullptr ) return false; return // compare the base class ( static_cast< const AbstractCartesianDiagram* >( this )->compare( other ) ) && // compare own properties ( type() == other->type() ); } void Plotter::connectAttributesModel( AttributesModel* newModel ) { // Order of setting the AttributesModel in compressor and diagram is very important due to slot // invocation order. Refer to the longer comment in // AbstractCartesianDiagram::connectAttributesModel() for details. if ( useDataCompression() == Plotter::NONE ) { d->plotterCompressor.setModel( nullptr ); AbstractCartesianDiagram::connectAttributesModel( newModel ); } else { d->compressor.setModel( nullptr ); if ( attributesModel() != d->plotterCompressor.model() ) { d->plotterCompressor.setModel( attributesModel() ); connect( &d->plotterCompressor, SIGNAL(boundariesChanged()), this, SLOT(setDataBoundariesDirty()) ); if ( useDataCompression() != Plotter::SLOPE ) { connect( coordinatePlane(), SIGNAL(internal_geometryChanged(QRect,QRect)), this, SLOT(setDataBoundariesDirty()) ); connect( coordinatePlane(), SIGNAL(geometryChanged(QRect,QRect)), this, SLOT(setDataBoundariesDirty()) ); calcMergeRadius(); } } } } Plotter::CompressionMode Plotter::useDataCompression() const { return d->implementor->useCompression(); } void Plotter::setUseDataCompression( Plotter::CompressionMode value ) { if ( useDataCompression() != value ) { d->implementor->setUseCompression( value ); if ( useDataCompression() != Plotter::NONE ) { d->compressor.setModel( nullptr ); if ( attributesModel() != d->plotterCompressor.model() ) d->plotterCompressor.setModel( attributesModel() ); } } } qreal Plotter::maxSlopeChange() const { return d->plotterCompressor.maxSlopeChange(); } void Plotter::setMaxSlopeChange( qreal value ) { d->plotterCompressor.setMaxSlopeChange( value ); } qreal Plotter::mergeRadiusPercentage() const { return d->mergeRadiusPercentage; } void Plotter::setMergeRadiusPercentage( qreal value ) { if ( d->mergeRadiusPercentage != value ) { d->mergeRadiusPercentage = value; //d->plotterCompressor.setMergeRadiusPercentage( value ); //update(); } } -/** - * Sets the plotter's type to \a type - */ void Plotter::setType( const PlotType type ) { if ( d->implementor->type() == type ) { return; } if ( datasetDimension() != 2 ) { Q_ASSERT_X ( false, "setType()", "This line chart type can only be used with two-dimensional data." ); return; } switch ( type ) { case Normal: d->implementor = d->normalPlotter; break; case Percent: d->implementor = d->percentPlotter; break; case Stacked: d->implementor = d->stackedPlotter; break; default: Q_ASSERT_X( false, "Plotter::setType", "unknown plotter subtype" ); } bool connection = connect( this, SIGNAL(boundariesChanged()), d->implementor->plotterPrivate(), SLOT(changedProperties()) ); Q_ASSERT( connection ); Q_UNUSED( connection ); // d->lineType = type; Q_ASSERT( d->implementor->type() == type ); setDataBoundariesDirty(); emit layoutChanged( this ); emit propertiesChanged(); } -/** - * @return the type of the plotter - */ Plotter::PlotType Plotter::type() const { return d->implementor->type(); } -/** - * Sets the global line attributes to \a la - */ void Plotter::setLineAttributes( const LineAttributes& la ) { d->attributesModel->setModelData( QVariant::fromValue( la ), LineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the line attributes of data set \a column to \a la - */ void Plotter::setLineAttributes( int column, const LineAttributes& la ) { d->setDatasetAttrs( column, QVariant::fromValue( la ), LineAttributesRole ); emit propertiesChanged(); } -/** - * Resets the line attributes of data set \a column - */ void Plotter::resetLineAttributes( int column ) { d->resetDatasetAttrs( column, LineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the line attributes for the model index \a index to \a la - */ void Plotter::setLineAttributes( const QModelIndex & index, const LineAttributes& la ) { d->attributesModel->setData( d->attributesModel->mapFromSource( index ), QVariant::fromValue( la ), LineAttributesRole ); emit propertiesChanged(); } -/** - * Remove any explicit line attributes settings that might have been specified before. - */ void Plotter::resetLineAttributes( const QModelIndex & index ) { d->attributesModel->resetData( d->attributesModel->mapFromSource(index), LineAttributesRole ); emit propertiesChanged(); } -/** - * @return the global line attribute set - */ LineAttributes Plotter::lineAttributes() const { return d->attributesModel->data( KChart::LineAttributesRole ).value(); } -/** - * @return the line attribute set of data set \a column - */ LineAttributes Plotter::lineAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, LineAttributesRole ) ); if ( attrs.isValid() ) return attrs.value(); return lineAttributes(); } -/** - * @return the line attribute set of the model index \a index - */ LineAttributes Plotter::lineAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::LineAttributesRole ).value(); } -/** - * Sets the global 3D line attributes to \a la - */ void Plotter::setThreeDLineAttributes( const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->attributesModel->setModelData( QVariant::fromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the 3D line attributes of data set \a column to \a la - */ void Plotter::setThreeDLineAttributes( int column, const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->setDatasetAttrs( column, QVariant::fromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } -/** - * Sets the 3D line attributes of model index \a index to \a la - */ void Plotter::setThreeDLineAttributes( const QModelIndex& index, const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->attributesModel->setData( d->attributesModel->mapFromSource( index ), QVariant::fromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } -/** - * @return the global 3D line attributes - */ ThreeDLineAttributes Plotter::threeDLineAttributes() const { return d->attributesModel->data( KChart::ThreeDLineAttributesRole ).value(); } -/** - * @return the 3D line attributes of data set \a column - */ ThreeDLineAttributes Plotter::threeDLineAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, ThreeDLineAttributesRole ) ); if ( attrs.isValid() ) { return attrs.value(); } return threeDLineAttributes(); } -/** - * @return the 3D line attributes of the model index \a index - */ ThreeDLineAttributes Plotter::threeDLineAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::ThreeDLineAttributesRole ).value(); } qreal Plotter::threeDItemDepth( const QModelIndex & index ) const { return threeDLineAttributes( index ).validDepth(); } qreal Plotter::threeDItemDepth( int column ) const { return threeDLineAttributes( column ).validDepth(); } -/** - * Sets the value tracker attributes of the model index \a index to \a va - */ void Plotter::setValueTrackerAttributes( const QModelIndex & index, const ValueTrackerAttributes & va ) { d->attributesModel->setData( d->attributesModel->mapFromSource( index ), QVariant::fromValue( va ), KChart::ValueTrackerAttributesRole ); emit propertiesChanged(); } -/** - * Returns the value tracker attributes of the model index \a index - */ ValueTrackerAttributes Plotter::valueTrackerAttributes( const QModelIndex & index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::ValueTrackerAttributesRole ).value(); } void Plotter::resizeEvent ( QResizeEvent* ) { } const QPair< QPointF, QPointF > Plotter::calculateDataBoundaries() const { if ( !checkInvariants( true ) ) return QPair< QPointF, QPointF >( QPointF( 0, 0 ), QPointF( 0, 0 ) ); // note: calculateDataBoundaries() is ignoring the hidden flags. // That's not a bug but a feature: Hiding data does not mean removing them. // For totally removing data from KD Chart's view people can use e.g. a proxy model ... // calculate boundaries for different line types Normal - Stacked - Percent - Default Normal return d->implementor->calculateDataBoundaries(); } void Plotter::paintEvent ( QPaintEvent*) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle ( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void Plotter::paint( PaintContext* ctx ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants( true ) ) return; AbstractCoordinatePlane* const plane = ctx->coordinatePlane(); if ( ! plane ) return; d->setCompressorResolution( size(), plane ); if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return; const PainterSaver p( ctx->painter() ); if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) == 0 ) return; // nothing to paint for us ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( ctx->painter() ) ); // paint different line types Normal - Stacked - Percent - Default Normal d->implementor->paint( ctx ); ctx->setCoordinatePlane( plane ); } void Plotter::resize ( const QSizeF& size ) { d->setCompressorResolution( size, coordinatePlane() ); if ( useDataCompression() == Plotter::BOTH || useDataCompression() == Plotter::DISTANCE ) { d->plotterCompressor.cleanCache(); calcMergeRadius(); } setDataBoundariesDirty(); AbstractCartesianDiagram::resize( size ); } void Plotter::setDataBoundariesDirty() { AbstractCartesianDiagram::setDataBoundariesDirty(); if ( useDataCompression() == Plotter::DISTANCE || useDataCompression() == Plotter::BOTH ) { calcMergeRadius(); //d->plotterCompressor.setMergeRadiusPercentage( d->mergeRadiusPercentage ); } } void Plotter::calcMergeRadius() { CartesianCoordinatePlane *plane = dynamic_cast< CartesianCoordinatePlane* >( coordinatePlane() ); Q_ASSERT( plane ); //Q_ASSERT( plane->translate( plane->translateBack( plane->visibleDiagramArea().topLeft() ) ) == plane->visibleDiagramArea().topLeft() ); QRectF range = plane->visibleDataRange(); //qDebug() << range; const qreal radius = std::sqrt( ( range.x() + range.width() ) * ( range.y() + range.height() ) ); //qDebug() << radius; //qDebug() << radius * d->mergeRadiusPercentage; //qDebug() << d->mergeRadiusPercentage; d->plotterCompressor.setMergeRadius( radius * d->mergeRadiusPercentage ); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int Plotter::numberOfAbscissaSegments () const { return d->attributesModel->rowCount( attributesModelRootIndex() ); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int Plotter::numberOfOrdinateSegments () const { return d->attributesModel->columnCount( attributesModelRootIndex() ); } diff --git a/src/KChart/Cartesian/KChartPlotter.h b/src/KChart/Cartesian/KChartPlotter.h index 65054c0..d80ad45 100644 --- a/src/KChart/Cartesian/KChartPlotter.h +++ b/src/KChart/Cartesian/KChartPlotter.h @@ -1,141 +1,213 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTPLOTTER_H #define KCHARTPLOTTER_H #include "KChartAbstractCartesianDiagram.h" #include "KChartLineAttributes.h" #include "KChartValueTrackerAttributes.h" namespace KChart { class ThreeDLineAttributes; /** * @brief Plotter defines a diagram type plotting two-dimensional data. */ class KCHART_EXPORT Plotter : public AbstractCartesianDiagram { Q_OBJECT Q_DISABLE_COPY( Plotter ) Q_ENUMS( CompressionMode ) KCHART_DECLARE_DERIVED_DIAGRAM( Plotter, CartesianCoordinatePlane ) Q_PROPERTY( CompressionMode useDataCompression READ useDataCompression WRITE setUseDataCompression ) Q_PROPERTY( qreal mergeRadiusPercentage READ mergeRadiusPercentage WRITE setMergeRadiusPercentage ) public: // SLOPE enables a compression based on minmal slope changes // DISTANCE is still buggy and can fail, same for BOTH, NONE is the default mode enum CompressionMode{ SLOPE, DISTANCE, BOTH, NONE }; class PlotterType; friend class PlotterType; explicit Plotter( QWidget* parent = nullptr, CartesianCoordinatePlane* plane = nullptr ); virtual ~Plotter(); - virtual Plotter* clone() const; + + /** + * Creates an exact copy of this diagram. + */ + virtual Plotter* clone() const; /** * Returns true if both diagrams have the same settings. */ bool compare( const Plotter* other ) const; enum PlotType { Normal = 0, Percent, Stacked }; - void setType( const PlotType type ); + + /** + * Sets the plotter's type to \a type + */ + void setType( const PlotType type ); + + /** + * @return the type of the plotter + */ PlotType type() const; - void setLineAttributes( const LineAttributes & a ); - void setLineAttributes( int column, const LineAttributes & a ); - void setLineAttributes( const QModelIndex & index, const LineAttributes & a ); + /** + * Sets the global line attributes to \a la + */ + void setLineAttributes( const LineAttributes & la ); + + /** + * Sets the line attributes of data set \a column to \a la + */ + void setLineAttributes( int column, const LineAttributes &la ); + + /** + * Sets the line attributes for the model index \a index to \a la + */ + void setLineAttributes( const QModelIndex & index, const LineAttributes &la ); + + /** + * Resets the line attributes of data set \a column + */ void resetLineAttributes( int column ); + + /** + * Remove any explicit line attributes settings that might have been specified before. + */ void resetLineAttributes( const QModelIndex & index ); + + /** + * @return the global line attribute set + */ LineAttributes lineAttributes() const; - LineAttributes lineAttributes( int column ) const; + + /** + * @return the line attribute set of data set \a column + */ + LineAttributes lineAttributes( int column ) const; + + /** + * @return the line attribute set of the model index \a index + */ LineAttributes lineAttributes( const QModelIndex & index ) const; - void setThreeDLineAttributes( const ThreeDLineAttributes & a ); - void setThreeDLineAttributes( int column, const ThreeDLineAttributes & a ); + /** + * Sets the global 3D line attributes to \a la + */ + void setThreeDLineAttributes( const ThreeDLineAttributes & la ); + + /** + * Sets the 3D line attributes of data set \a column to \a la + */ + void setThreeDLineAttributes( int column, const ThreeDLineAttributes & la ); + + /** + * Sets the 3D line attributes of model index \a index to \a la + */ void setThreeDLineAttributes( const QModelIndex & index, - const ThreeDLineAttributes & a ); + const ThreeDLineAttributes & la ); + /** + * @return the global 3D line attributes + */ ThreeDLineAttributes threeDLineAttributes() const; + + /** + * @return the 3D line attributes of data set \a column + */ ThreeDLineAttributes threeDLineAttributes( int column ) const; + + /** + * @return the 3D line attributes of the model index \a index + */ ThreeDLineAttributes threeDLineAttributes( const QModelIndex & index ) const; - void setValueTrackerAttributes( const QModelIndex & index, - const ValueTrackerAttributes & a ); - ValueTrackerAttributes valueTrackerAttributes( const QModelIndex & index ) const; + /** + * Sets the value tracker attributes of the model index \a index to \a va + */ + void setValueTrackerAttributes( const QModelIndex & index, + const ValueTrackerAttributes & va ); + + /** + * Returns the value tracker attributes of the model index \a index + */ + ValueTrackerAttributes valueTrackerAttributes( const QModelIndex & index ) const; CompressionMode useDataCompression() const; void setUseDataCompression( CompressionMode value ); qreal maxSlopeChange() const; void setMaxSlopeChange( qreal value ); qreal mergeRadiusPercentage() const; void setMergeRadiusPercentage( qreal value ); #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) // implement AbstractCartesianDiagram /* reimpl */ const int numberOfAbscissaSegments () const; /* reimpl */ const int numberOfOrdinateSegments () const; #else // implement AbstractCartesianDiagram /* reimpl */ int numberOfAbscissaSegments () const override; /* reimpl */ int numberOfOrdinateSegments () const override; #endif protected Q_SLOTS: void connectAttributesModel( AttributesModel* ); protected: void paint ( PaintContext* paintContext ) override; public: void resize ( const QSizeF& area ) override; protected: qreal threeDItemDepth( const QModelIndex & index ) const override; qreal threeDItemDepth( int column ) const override; /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent ( QPaintEvent* ) override; void resizeEvent ( QResizeEvent* ) override; protected Q_SLOTS: void setDataBoundariesDirty(); void calcMergeRadius(); }; // End of class KChart::Plotter } #endif // KCHARTLINEDIAGRAM_H diff --git a/src/KChart/Cartesian/KChartStockBarAttributes.cpp b/src/KChart/Cartesian/KChartStockBarAttributes.cpp index 4aa89cf..dca6b3a 100644 --- a/src/KChart/Cartesian/KChartStockBarAttributes.cpp +++ b/src/KChart/Cartesian/KChartStockBarAttributes.cpp @@ -1,104 +1,88 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartStockBarAttributes.h" #define d d_func() using namespace KChart; class Q_DECL_HIDDEN StockBarAttributes::Private { public: Private(); qreal candlestickWidth; qreal tickLength; }; StockBarAttributes::Private::Private() : candlestickWidth( 0.3 ) , tickLength( 0.15 ) { } StockBarAttributes::StockBarAttributes() : _d( new Private ) { } StockBarAttributes::StockBarAttributes( const StockBarAttributes& r ) : _d( new Private( *r.d ) ) { } StockBarAttributes &StockBarAttributes::operator= ( const StockBarAttributes& r ) { if ( this == &r ) return *this; *d = *r.d; return *this; } StockBarAttributes::~StockBarAttributes() { delete _d; } -/** - * Sets the width of a candlestick - * - * @param width The width of a candlestick - */ void StockBarAttributes::setCandlestickWidth( qreal width ) { d->candlestickWidth = width; } -/** - * @return the width of a candlestick - */ qreal StockBarAttributes::candlestickWidth() const { return d->candlestickWidth; } -/** - * Sets the tick length of both the open and close marker - * - * @param length the tick length - */ void StockBarAttributes::setTickLength( qreal length ) { d->tickLength = length; } -/** - * @return the tick length used for both the open and close marker - */ qreal StockBarAttributes::tickLength() const { return d->tickLength; } bool StockBarAttributes::operator==( const StockBarAttributes& r ) const { return candlestickWidth() == r.candlestickWidth() && tickLength() == r.tickLength(); } diff --git a/src/KChart/Cartesian/KChartStockBarAttributes.h b/src/KChart/Cartesian/KChartStockBarAttributes.h index cfeff65..39716ce 100644 --- a/src/KChart/Cartesian/KChartStockBarAttributes.h +++ b/src/KChart/Cartesian/KChartStockBarAttributes.h @@ -1,60 +1,79 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTSTOCKBARATTRIBUTES_H #define KCHARTSTOCKBARATTRIBUTES_H #include #include "KChartGlobal.h" namespace KChart { /** * @brief Attributes to customize the appearance of a column in a stock chart */ class KCHART_EXPORT StockBarAttributes { public: StockBarAttributes(); StockBarAttributes( const StockBarAttributes& ); StockBarAttributes &operator= ( const StockBarAttributes& ); ~StockBarAttributes(); + + /** + * Sets the width of a candlestick + * + * @param width The width of a candlestick + */ void setCandlestickWidth( qreal width ); + + /** + * @return the width of a candlestick + */ qreal candlestickWidth() const; + /** + * Sets the tick length of both the open and close marker + * + * @param length the tick length + */ void setTickLength( qreal length ); + + /** + * @return the tick length used for both the open and close marker + */ qreal tickLength() const; bool operator==( const StockBarAttributes& ) const; inline bool operator!=( const StockBarAttributes& other ) const { return !operator==(other); } private: class Private; Private * _d; Private * d_func() { return _d; } const Private * d_func() const { return _d; } }; } Q_DECLARE_METATYPE( KChart::StockBarAttributes ) #endif // KCHARTSTOCKBARATTRIBUTES_H diff --git a/src/KChart/Cartesian/KChartStockDiagram.cpp b/src/KChart/Cartesian/KChartStockDiagram.cpp index b5abd2e..fbd0858 100644 --- a/src/KChart/Cartesian/KChartStockDiagram.cpp +++ b/src/KChart/Cartesian/KChartStockDiagram.cpp @@ -1,378 +1,343 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartStockDiagram.h" #include "KChartStockDiagram_p.h" #include "KChartPaintContext.h" #include "KChartPainterSaver_p.h" using namespace KChart; #define d d_func() StockDiagram::StockDiagram( QWidget *parent, CartesianCoordinatePlane *plane ) : AbstractCartesianDiagram( new Private(), parent, plane ) { init(); } StockDiagram::~StockDiagram() { } /* * Initializes the diagram */ void StockDiagram::init() { d->diagram = this; d->compressor.setModel( attributesModel() ); // Set properties to defaults d->type = HighLowClose; d->upTrendCandlestickBrush = QBrush( Qt::white ); d->downTrendCandlestickBrush = QBrush( Qt::black ); d->upTrendCandlestickPen = QPen( Qt::black ); d->downTrendCandlestickPen = QPen( Qt::black ); d->lowHighLinePen = QPen( Qt::black ); setDatasetDimensionInternal( 3 ); //setDatasetDimension( 3 ); setPen( QPen( Qt::black ) ); } -/** - * Switches between the supported types of stock charts, - * depending on \a type - */ void StockDiagram::setType( Type type ) { d->type = type; emit propertiesChanged(); } -/** - * @return the type of this diagram - */ StockDiagram::Type StockDiagram::type() const { return d->type; } void StockDiagram::setStockBarAttributes( const StockBarAttributes &attr ) { attributesModel()->setModelData( QVariant::fromValue( attr ), StockBarAttributesRole ); emit propertiesChanged(); } StockBarAttributes StockDiagram::stockBarAttributes() const { return attributesModel()->modelData( StockBarAttributesRole ).value(); } void StockDiagram::setStockBarAttributes( int column, const StockBarAttributes &attr ) { d->setDatasetAttrs( column, QVariant::fromValue( attr ), StockBarAttributesRole ); emit propertiesChanged(); } StockBarAttributes StockDiagram::stockBarAttributes( int column ) const { const QVariant attr( d->datasetAttrs( column, StockBarAttributesRole ) ); if ( attr.isValid() ) return attr.value(); return stockBarAttributes(); } -/** - * Sets the 3D attributes for all bars (i.e. candlesticks) - * - * @param attr The 3D attributes to set - */ void StockDiagram::setThreeDBarAttributes( const ThreeDBarAttributes &attr ) { attributesModel()->setModelData( QVariant::fromValue( attr ), ThreeDBarAttributesRole ); emit propertiesChanged(); } -/** - * Returns the 3D attributes for all bars (i.e. candlesticks) - * - * @return the 3D bar attributes - */ ThreeDBarAttributes StockDiagram::threeDBarAttributes() const { return attributesModel()->modelData( ThreeDBarAttributesRole ).value(); } -/** - * Sets the 3D attributes for the bar (i.e. candlestick) in certain column - * of the diagram - * - * Note: Every column in a StockDiagram is represented by a row in the model - * - * @param column The column to set the 3D bar attributes for - * @param attr The 3D attributes to set - */ void StockDiagram::setThreeDBarAttributes( int column, const ThreeDBarAttributes &attr ) { d->setDatasetAttrs( column, QVariant::fromValue( attr ), StockBarAttributesRole ); emit propertiesChanged(); } -/** - * Returns the 3D attributes for a bars (i.e. candlestick) in a certain column - * of the diagram - * - * Note: Every column in a StockDiagram is represented by a row in the model - * - * @param column The column to get the 3D bar attributes for - * @return The 3D attributes for the specified column - */ ThreeDBarAttributes StockDiagram::threeDBarAttributes( int column ) const { const QVariant attr( d->datasetAttrs( column, ThreeDBarAttributesRole ) ); if ( attr.isValid() ) return attr.value(); return threeDBarAttributes(); } void StockDiagram::setLowHighLinePen( const QPen &pen ) { d->lowHighLinePen = pen; } QPen StockDiagram::lowHighLinePen() const { return d->lowHighLinePen; } void StockDiagram::setLowHighLinePen( int column, const QPen &pen ) { d->lowHighLinePens[column] = pen; } QPen StockDiagram::lowHighLinePen( int column ) const { if ( d->lowHighLinePens.contains( column ) ) return d->lowHighLinePens[column]; return d->lowHighLinePen; } void StockDiagram::setUpTrendCandlestickBrush( const QBrush &brush ) { d->upTrendCandlestickBrush = brush; } QBrush StockDiagram::upTrendCandlestickBrush() const { return d->upTrendCandlestickBrush; } void StockDiagram::setDownTrendCandlestickBrush( const QBrush &brush ) { d->downTrendCandlestickBrush = brush; } QBrush StockDiagram::downTrendCandlestickBrush() const { return d->downTrendCandlestickBrush; } void StockDiagram::setUpTrendCandlestickBrush( int column, const QBrush &brush ) { d->upTrendCandlestickBrushes[column] = brush; } QBrush StockDiagram::upTrendCandlestickBrush( int column ) const { if ( d->upTrendCandlestickBrushes.contains( column ) ) return d->upTrendCandlestickBrushes[column]; return d->upTrendCandlestickBrush; } void StockDiagram::setDownTrendCandlestickBrush( int column, const QBrush &brush ) { d->downTrendCandlestickBrushes[column] = brush; } QBrush StockDiagram::downTrendCandlestickBrush( int column ) const { if ( d->downTrendCandlestickBrushes.contains( column ) ) return d->downTrendCandlestickBrushes[column]; return d->downTrendCandlestickBrush; } void StockDiagram::setUpTrendCandlestickPen( const QPen &pen ) { d->upTrendCandlestickPen = pen; } QPen StockDiagram::upTrendCandlestickPen() const { return d->upTrendCandlestickPen; } void StockDiagram::setDownTrendCandlestickPen( const QPen &pen ) { d->downTrendCandlestickPen = pen; } QPen StockDiagram::downTrendCandlestickPen() const { return d->downTrendCandlestickPen; } void StockDiagram::setUpTrendCandlestickPen( int column, const QPen &pen ) { d->upTrendCandlestickPens[column] = pen; } QPen StockDiagram::upTrendCandlestickPen( int column ) const { if ( d->upTrendCandlestickPens.contains( column ) ) return d->upTrendCandlestickPens[column]; return d->upTrendCandlestickPen; } void StockDiagram::setDownTrendCandlestickPen( int column, const QPen &pen ) { d->downTrendCandlestickPens[column] = pen; } QPen StockDiagram::downTrendCandlestickPen( int column ) const { if ( d->downTrendCandlestickPens.contains( column ) ) return d->downTrendCandlestickPens[column]; return d->downTrendCandlestickPen; } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int StockDiagram::numberOfAbscissaSegments() const { return 1; } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int StockDiagram::numberOfOrdinateSegments() const { return 1; } void StockDiagram::paint( PaintContext *context ) { // Clear old reverse mapping data and create new // reverse mapping scene d->reverseMapper.clear(); PainterSaver painterSaver( context->painter() ); const int rowCount = attributesModel()->rowCount( attributesModelRootIndex() ); const int divisor = ( d->type == OpenHighLowClose || d->type == Candlestick ) ? 4 : 3; const int colCount = attributesModel()->columnCount( attributesModelRootIndex() ) / divisor; for ( int col = 0; col < colCount; ++col ) { for ( int row = 0; row < rowCount; row++ ) { CartesianDiagramDataCompressor::DataPoint low; CartesianDiagramDataCompressor::DataPoint high; CartesianDiagramDataCompressor::DataPoint open; CartesianDiagramDataCompressor::DataPoint close; CartesianDiagramDataCompressor::DataPoint volume; if ( d->type == HighLowClose ) { const CartesianDiagramDataCompressor::CachePosition highPos( row, col * divisor ); const CartesianDiagramDataCompressor::CachePosition lowPos( row, col * divisor + 1 ); const CartesianDiagramDataCompressor::CachePosition closePos( row, col * divisor + 2 ); low = d->compressor.data( lowPos ); high = d->compressor.data( highPos ); close = d->compressor.data( closePos ); } else if ( d->type == OpenHighLowClose || d->type == Candlestick ) { const CartesianDiagramDataCompressor::CachePosition openPos( row, col * divisor ); const CartesianDiagramDataCompressor::CachePosition highPos( row, col * divisor + 1 ); const CartesianDiagramDataCompressor::CachePosition lowPos( row, col * divisor + 2 ); const CartesianDiagramDataCompressor::CachePosition closePos( row, col * divisor + 3 ); open = d->compressor.data( openPos ); low = d->compressor.data( lowPos ); high = d->compressor.data( highPos ); close = d->compressor.data( closePos ); } switch ( d->type ) { case HighLowClose: open.hidden = true; // Fall-through intended! case OpenHighLowClose: if ( close.index.isValid() && low.index.isValid() && high.index.isValid() ) d->drawOHLCBar( col, open, high, low, close, context ); break; case Candlestick: d->drawCandlestick( col, open, high, low, close, context ); break; } } } } void StockDiagram::resize( const QSizeF &size ) { d->compressor.setResolution( static_cast< int >( size.width() * coordinatePlane()->zoomFactorX() ), static_cast< int >( size.height() * coordinatePlane()->zoomFactorY() ) ); setDataBoundariesDirty(); AbstractCartesianDiagram::resize( size ); } qreal StockDiagram::threeDItemDepth( int column ) const { Q_UNUSED( column ); //FIXME: Implement threeD functionality return 1.0; } qreal StockDiagram::threeDItemDepth( const QModelIndex &index ) const { Q_UNUSED( index ); //FIXME: Implement threeD functionality return 1.0; } const QPair StockDiagram::calculateDataBoundaries() const { const int rowCount = attributesModel()->rowCount( attributesModelRootIndex() ); const int colCount = attributesModel()->columnCount( attributesModelRootIndex() ); qreal xMin = 0.0; qreal xMax = rowCount; qreal yMin = 0.0; qreal yMax = 0.0; for ( int row = 0; row < rowCount; row++ ) { for ( int col = 0; col < colCount; col++ ) { const CartesianDiagramDataCompressor::CachePosition pos( row, col ); const CartesianDiagramDataCompressor::DataPoint point = d->compressor.data( pos ); yMax = qMax( yMax, point.value ); yMin = qMin( yMin, point.value ); // FIXME: Can stock charts really have negative values? } } return QPair( QPointF( xMin, yMin ), QPointF( xMax, yMax ) ); } diff --git a/src/KChart/Cartesian/KChartStockDiagram.h b/src/KChart/Cartesian/KChartStockDiagram.h index 238d045..1f9b496 100644 --- a/src/KChart/Cartesian/KChartStockDiagram.h +++ b/src/KChart/Cartesian/KChartStockDiagram.h @@ -1,116 +1,157 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHART_STOCK_DIAGRAM_H #define KCHART_STOCK_DIAGRAM_H #include "KChartAbstractCartesianDiagram.h" #include "KChartCartesianCoordinatePlane.h" #include "KChartStockBarAttributes.h" #include "KChartThreeDBarAttributes.h" namespace KChart { class PaintContext; class KCHART_EXPORT StockDiagram : public AbstractCartesianDiagram { Q_OBJECT Q_DISABLE_COPY( StockDiagram ) KCHART_DECLARE_DERIVED_DIAGRAM( StockDiagram, CartesianCoordinatePlane ) public: enum Type { HighLowClose, OpenHighLowClose, Candlestick }; explicit StockDiagram( QWidget *parent = nullptr, CartesianCoordinatePlane *plane = nullptr ); ~StockDiagram(); - void setType( Type type ); - Type type() const; + + /** + * Switches between the supported types of stock charts, + * depending on \a type + */ + void setType( Type type ); + + /** + * @return the type of this diagram + */ + Type type() const; void setStockBarAttributes( const StockBarAttributes &attr ); StockBarAttributes stockBarAttributes() const; void setStockBarAttributes( int column, const StockBarAttributes &attr ); StockBarAttributes stockBarAttributes( int column ) const; - void setThreeDBarAttributes( const ThreeDBarAttributes &attr ); + + /** + * Sets the 3D attributes for all bars (i.e. candlesticks) + * + * @param attr The 3D attributes to set + */ + + /** + * Sets the 3D attributes for the bar (i.e. candlestick) in certain column + * of the diagram + * + * Note: Every column in a StockDiagram is represented by a row in the model + * + * @param column The column to set the 3D bar attributes for + * @param attr The 3D attributes to set + */ + void setThreeDBarAttributes( const ThreeDBarAttributes &attr ); + + /** + * Returns the 3D attributes for all bars (i.e. candlesticks) + * + * @return the 3D bar attributes + */ ThreeDBarAttributes threeDBarAttributes() const; - void setThreeDBarAttributes( int column, const ThreeDBarAttributes &attr ); + /** + * Returns the 3D attributes for a bars (i.e. candlestick) in a certain column + * of the diagram + * + * Note: Every column in a StockDiagram is represented by a row in the model + * + * @param column The column to get the 3D bar attributes for + * @return The 3D attributes for the specified column + */ ThreeDBarAttributes threeDBarAttributes( int column ) const; + void setThreeDBarAttributes( int column, const ThreeDBarAttributes &attr ); + void setLowHighLinePen( const QPen &pen ); QPen lowHighLinePen() const; void setLowHighLinePen( int column, const QPen &pen ); QPen lowHighLinePen( int column ) const; void setUpTrendCandlestickBrush( const QBrush &brush ); QBrush upTrendCandlestickBrush() const; void setDownTrendCandlestickBrush( const QBrush &brush ); QBrush downTrendCandlestickBrush() const; void setUpTrendCandlestickBrush( int column, const QBrush &brush ); QBrush upTrendCandlestickBrush( int column ) const; void setDownTrendCandlestickBrush( int column, const QBrush &brush ); QBrush downTrendCandlestickBrush( int column ) const; void setUpTrendCandlestickPen( const QPen &pen ); QPen upTrendCandlestickPen() const; void setDownTrendCandlestickPen( const QPen &pen ); QPen downTrendCandlestickPen() const; void setUpTrendCandlestickPen( int column, const QPen &pen ); QPen upTrendCandlestickPen( int column ) const; void setDownTrendCandlestickPen( int column, const QPen &pen ); QPen downTrendCandlestickPen( int column ) const; #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) virtual const int numberOfAbscissaSegments() const; virtual const int numberOfOrdinateSegments() const; #else int numberOfAbscissaSegments() const override; int numberOfOrdinateSegments() const override; #endif void paint( PaintContext *paintContext ) override; void resize( const QSizeF &size ) override; qreal threeDItemDepth( int column ) const override; qreal threeDItemDepth( const QModelIndex &index ) const override; protected: const QPair calculateDataBoundaries() const override; }; } // namespace KChart #endif // KCHART_STOCK_DIAGRAM_H diff --git a/src/KChart/KChartAbstractDiagram.cpp b/src/KChart/KChartAbstractDiagram.cpp index dff27ee..21d538b 100644 --- a/src/KChart/KChartAbstractDiagram.cpp +++ b/src/KChart/KChartAbstractDiagram.cpp @@ -1,1198 +1,1195 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAbstractDiagram.h" #include "KChartAbstractDiagram_p.h" #include #include #include #include #include #include #include "KChartAbstractCoordinatePlane.h" #include "KChartChart.h" #include "KChartDataValueAttributes.h" #include "KChartTextAttributes.h" #include "KChartMarkerAttributes.h" #include "KChartAbstractThreeDAttributes.h" #include "KChartThreeDLineAttributes.h" #include "KChartPainterSaver_p.h" #include using namespace KChart; #define d d_func() AbstractDiagram::AbstractDiagram ( QWidget* parent, AbstractCoordinatePlane* plane ) : QAbstractItemView ( parent ), _d( new Private() ) { _d->init( plane ); init(); } AbstractDiagram::~AbstractDiagram() { emit aboutToBeDestroyed(); delete _d; } void AbstractDiagram::init() { _d->diagram = this; d->reverseMapper.setDiagram( this ); } bool AbstractDiagram::compare( const AbstractDiagram* other ) const { if ( other == this ) return true; if ( !other ) { return false; } return // compare QAbstractScrollArea properties (horizontalScrollBarPolicy() == other->horizontalScrollBarPolicy()) && (verticalScrollBarPolicy() == other->verticalScrollBarPolicy()) && // compare QFrame properties (frameShadow() == other->frameShadow()) && (frameShape() == other->frameShape()) && // frameWidth is a read-only property defined by the style, it should not be in here: // (frameWidth() == other->frameWidth()) && (lineWidth() == other->lineWidth()) && (midLineWidth() == other->midLineWidth()) && // compare QAbstractItemView properties (alternatingRowColors() == other->alternatingRowColors()) && (hasAutoScroll() == other->hasAutoScroll()) && (dragDropMode() == other->dragDropMode()) && (dragDropOverwriteMode() == other->dragDropOverwriteMode()) && (horizontalScrollMode() == other->horizontalScrollMode ()) && (verticalScrollMode() == other->verticalScrollMode()) && (dragEnabled() == other->dragEnabled()) && (editTriggers() == other->editTriggers()) && (iconSize() == other->iconSize()) && (selectionBehavior() == other->selectionBehavior()) && (selectionMode() == other->selectionMode()) && (showDropIndicator() == other->showDropIndicator()) && (tabKeyNavigation() == other->tabKeyNavigation()) && (textElideMode() == other->textElideMode()) && // compare all of the properties stored in the attributes model attributesModel()->compare( other->attributesModel() ) && // compare own properties (rootIndex().column() == other->rootIndex().column()) && (rootIndex().row() == other->rootIndex().row()) && (allowOverlappingDataValueTexts() == other->allowOverlappingDataValueTexts()) && (antiAliasing() == other->antiAliasing()) && (percentMode() == other->percentMode()) && (datasetDimension() == other->datasetDimension()); } AbstractCoordinatePlane* AbstractDiagram::coordinatePlane() const { return d->plane; } const QPair AbstractDiagram::dataBoundaries () const { if ( d->databoundariesDirty ) { d->databoundaries = calculateDataBoundaries (); d->databoundariesDirty = false; } return d->databoundaries; } void AbstractDiagram::setDataBoundariesDirty() const { d->databoundariesDirty = true; update(); } void AbstractDiagram::resize(const QSizeF& size) { d->diagramSize = size; QAbstractItemView::resize( size.toSize() ); } void AbstractDiagram::setModel( QAbstractItemModel * newModel ) { if ( newModel == model() ) { return; } AttributesModel* amodel = new PrivateAttributesModel( newModel, this ); amodel->initFrom( d->attributesModel ); d->setAttributesModel(amodel); QAbstractItemView::setModel( newModel ); scheduleDelayedItemsLayout(); setDataBoundariesDirty(); emit modelsChanged(); } void AbstractDiagram::setSelectionModel( QItemSelectionModel* newSelectionModel ) { if ( selectionModel() ) { disconnect( selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SIGNAL(modelsChanged()) ); disconnect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(modelsChanged()) ); } QAbstractItemView::setSelectionModel( newSelectionModel ); if ( selectionModel() ) { connect( selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SIGNAL(modelsChanged()) ); connect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(modelsChanged()) ); } emit modelsChanged(); } /* Sets an external AttributesModel on this diagram. By default, a diagram has it's own internal set of attributes, but an external one can be set. This can be used to share attributes between several diagrams. The diagram does not take ownership of the attributesmodel. @param amodel The AttributesModel to use for this diagram. */ void AbstractDiagram::setAttributesModel( AttributesModel* amodel ) { if ( amodel->sourceModel() != model() ) { qWarning("KChart::AbstractDiagram::setAttributesModel() failed: " "Trying to set an attributesmodel which works on a different " "model than the diagram."); return; } if ( qobject_cast(amodel) ) { qWarning("KChart::AbstractDiagram::setAttributesModel() failed: " "Trying to set an attributesmodel that is private to another diagram."); return; } d->setAttributesModel( amodel ); scheduleDelayedItemsLayout(); setDataBoundariesDirty(); emit modelsChanged(); } bool AbstractDiagram::usesExternalAttributesModel() const { return d->usesExternalAttributesModel(); } AttributesModel* AbstractDiagram::attributesModel() const { return d->attributesModel; } QModelIndex AbstractDiagram::conditionallyMapFromSource( const QModelIndex & index ) const { Q_ASSERT( !index.isValid() || index.model() == attributesModel() || index.model() == attributesModel()->sourceModel() ); return index.model() == attributesModel() ? index : attributesModel()->mapFromSource( index ); } /* \reimpl */ void AbstractDiagram::setRootIndex( const QModelIndex& idx ) { QAbstractItemView::setRootIndex( idx ); setAttributesModelRootIndex( d->attributesModel->mapFromSource( idx ) ); } -/*! \internal */ void AbstractDiagram::setAttributesModelRootIndex( const QModelIndex& idx ) { d->attributesModelRootIndex = idx; setDataBoundariesDirty(); scheduleDelayedItemsLayout(); } -/*! returns a QModelIndex pointing into the AttributesModel that corresponds to the - root index of the diagram. */ QModelIndex AbstractDiagram::attributesModelRootIndex() const { if ( !d->attributesModelRootIndex.isValid() ) d->attributesModelRootIndex = d->attributesModel->mapFromSource( rootIndex() ); return d->attributesModelRootIndex; } void AbstractDiagram::setCoordinatePlane( AbstractCoordinatePlane* parent ) { d->plane = parent; } void AbstractDiagram::doItemsLayout() { if ( d->plane ) { d->plane->layoutDiagrams(); update(); } QAbstractItemView::doItemsLayout(); } void AbstractDiagram::dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector & ) { Q_UNUSED( topLeft ); Q_UNUSED( bottomRight ); // We are still too dumb to do intelligent updates... setDataBoundariesDirty(); scheduleDelayedItemsLayout(); } void AbstractDiagram::setHidden( const QModelIndex & index, bool hidden ) { d->attributesModel->setData( conditionallyMapFromSource( index ), QVariant::fromValue( hidden ), DataHiddenRole ); emit dataHidden(); } void AbstractDiagram::setHidden( int dataset, bool hidden ) { d->setDatasetAttrs( dataset, QVariant::fromValue( hidden ), DataHiddenRole ); emit dataHidden(); } void AbstractDiagram::setHidden( bool hidden ) { d->attributesModel->setModelData( QVariant::fromValue( hidden ), DataHiddenRole ); emit dataHidden(); } bool AbstractDiagram::isHidden() const { return attributesModel()->modelData( DataHiddenRole ).value< bool >(); } bool AbstractDiagram::isHidden( int dataset ) const { const QVariant boolFlag( d->datasetAttrs( dataset, DataHiddenRole ) ); if ( boolFlag.isValid() ) return boolFlag.value< bool >(); return isHidden(); } bool AbstractDiagram::isHidden( const QModelIndex & index ) const { const QVariant boolFlag( attributesModel()->data( conditionallyMapFromSource( index ), DataHiddenRole ) ); if ( boolFlag.isValid() ) { return boolFlag.value< bool >(); } int dataset = index.column() / d->datasetDimension; return isHidden( dataset ); } void AbstractDiagram::setDataValueAttributes( const QModelIndex & index, const DataValueAttributes & a ) { d->attributesModel->setData( conditionallyMapFromSource( index ), QVariant::fromValue( a ), DataValueLabelAttributesRole ); emit propertiesChanged(); } void AbstractDiagram::setDataValueAttributes( int dataset, const DataValueAttributes & a ) { d->setDatasetAttrs( dataset, QVariant::fromValue( a ), DataValueLabelAttributesRole ); emit propertiesChanged(); } DataValueAttributes AbstractDiagram::dataValueAttributes() const { return attributesModel()->modelData( KChart::DataValueLabelAttributesRole ).value< DataValueAttributes >(); } DataValueAttributes AbstractDiagram::dataValueAttributes( int dataset ) const { /* The following did not work! (khz, 2008-01-25) If there was some attrs specified for the 0-th cells of a dataset, then this logic would return the cell's settings instead of the header settings: return qVariantValue( attributesModel()->data( attributesModel()->mapFromSource(columnToIndex( column )), KChart::DataValueLabelAttributesRole ) ); */ const QVariant headerAttrs( d->datasetAttrs( dataset, KChart::DataValueLabelAttributesRole ) ); if ( headerAttrs.isValid() ) return headerAttrs.value< DataValueAttributes >(); return dataValueAttributes(); } DataValueAttributes AbstractDiagram::dataValueAttributes( const QModelIndex & index ) const { return attributesModel()->data( conditionallyMapFromSource( index ), KChart::DataValueLabelAttributesRole ).value< DataValueAttributes >(); } void AbstractDiagram::setDataValueAttributes( const DataValueAttributes & a ) { d->attributesModel->setModelData( QVariant::fromValue( a ), DataValueLabelAttributesRole ); emit propertiesChanged(); } void AbstractDiagram::setAllowOverlappingDataValueTexts( bool allow ) { DataValueAttributes attrs = dataValueAttributes(); attrs.setShowOverlappingDataLabels( allow ); setDataValueAttributes( attrs ); d->allowOverlappingDataValueTexts = allow; emit propertiesChanged(); } bool AbstractDiagram::allowOverlappingDataValueTexts() const { return d->allowOverlappingDataValueTexts; } void AbstractDiagram::setAntiAliasing( bool enabled ) { d->antiAliasing = enabled; emit propertiesChanged(); } bool AbstractDiagram::antiAliasing() const { return d->antiAliasing; } void AbstractDiagram::setPercentMode ( bool percent ) { d->percent = percent; emit propertiesChanged(); } bool AbstractDiagram::percentMode() const { return d->percent; } void AbstractDiagram::paintDataValueText( QPainter* painter, const QModelIndex& index, const QPointF& pos, qreal value ) { d->paintDataValueText( painter, index, pos, value ); } void AbstractDiagram::paintDataValueTexts( QPainter* painter ) { if ( !checkInvariants() ) { return; } d->forgetAlreadyPaintedDataValues(); const int rowCount = model()->rowCount( rootIndex() ); const int columnCount = model()->columnCount( rootIndex() ); for ( int column = 0; column < columnCount; column += datasetDimension() ) { for ( int row = 0; row < rowCount; ++row ) { QModelIndex index = model()->index( row, column, rootIndex() ); // checked qreal x; qreal y; if ( datasetDimension() == 1 ) { x = row; y = index.data().toReal(); } else { x = index.data().toReal(); y = model()->index( row, column + 1, rootIndex() ).data().toReal(); } paintDataValueText( painter, index, coordinatePlane()->translate( QPointF( x, y ) ), y ); } } } void AbstractDiagram::paintMarker( QPainter* painter, const DataValueAttributes& a, const QModelIndex& index, const QPointF& pos ) { if ( !checkInvariants() || !a.isVisible() ) return; const MarkerAttributes ma = a.markerAttributes(); if ( !ma.isVisible() ) return; const PainterSaver painterSaver( painter ); QSizeF maSize = ma.markerSize(); const qreal diagramWidth = d->diagramSize.width(); const qreal diagramHeight = d->diagramSize.height(); switch( ma.markerSizeMode() ) { case MarkerAttributes::AbsoluteSize: // Unscaled, i.e. without the painter's "zoom" maSize.rwidth() /= painter->matrix().m11(); maSize.rheight() /= painter->matrix().m22(); break; case MarkerAttributes::AbsoluteSizeScaled: // Keep maSize as is. It is specified directly in pixels and desired // to be effected by the painter's "zoom". break; case MarkerAttributes::RelativeToDiagramWidthHeightMin: maSize *= qMin( diagramWidth, diagramHeight ); break; } QBrush indexBrush( brush( index ) ); QPen indexPen( ma.pen() ); if ( ma.markerColor().isValid() ) indexBrush.setColor( ma.markerColor() ); paintMarker( painter, ma, indexBrush, indexPen, pos, maSize ); // workaround: BC cannot be changed, otherwise we would pass the // index down to next-lower paintMarker function. So far, we // basically save a circle of radius maSize at pos in the // reverseMapper. This means that ^^^ this version of paintMarker // needs to be called to reverse-map the marker. d->reverseMapper.addCircle( index.row(), index.column(), pos, 2 * maSize ); } void AbstractDiagram::paintMarker( QPainter* painter, const QModelIndex& index, const QPointF& pos ) { if ( !checkInvariants() ) return; paintMarker( painter, dataValueAttributes( index ), index, pos ); } void AbstractDiagram::paintMarker( QPainter* painter, const MarkerAttributes& markerAttributes, const QBrush& brush, const QPen& pen, const QPointF& pos, const QSizeF& maSize ) { const QPen oldPen( painter->pen() ); // Pen is used to paint 4Pixels - 1 Pixel - Ring and FastCross types. // make sure to use the brush color - see above in those cases. const bool isFourPixels = (markerAttributes.markerStyle() == MarkerAttributes::Marker4Pixels); if ( isFourPixels || (markerAttributes.markerStyle() == MarkerAttributes::Marker1Pixel) ) { // for high-performance point charts with tiny point markers: painter->setPen( PrintingParameters::scalePen( QPen( brush.color().lighter() ) ) ); if ( isFourPixels ) { const qreal x = pos.x(); const qreal y = pos.y(); painter->drawLine( QPointF(x-1.0,y-1.0), QPointF(x+1.0,y-1.0) ); painter->drawLine( QPointF(x-1.0,y), QPointF(x+1.0,y) ); painter->drawLine( QPointF(x-1.0,y+1.0), QPointF(x+1.0,y+1.0) ); } painter->drawPoint( pos ); } else { const PainterSaver painterSaver( painter ); QPen painterPen( pen ); painter->setPen( PrintingParameters::scalePen( painterPen ) ); painter->setBrush( brush ); painter->setRenderHint ( QPainter::Antialiasing ); painter->translate( pos ); switch ( markerAttributes.markerStyle() ) { case MarkerAttributes::MarkerCircle: { if ( markerAttributes.threeD() ) { QRadialGradient grad; grad.setCoordinateMode( QGradient::ObjectBoundingMode ); QColor drawColor = brush.color(); grad.setCenter( 0.5, 0.5 ); grad.setRadius( 1.0 ); grad.setFocalPoint( 0.35, 0.35 ); grad.setColorAt( 0.00, drawColor.lighter( 150 ) ); grad.setColorAt( 0.20, drawColor ); grad.setColorAt( 0.50, drawColor.darker( 150 ) ); grad.setColorAt( 0.75, drawColor.darker( 200 ) ); grad.setColorAt( 0.95, drawColor.darker( 250 ) ); grad.setColorAt( 1.00, drawColor.darker( 200 ) ); QBrush newBrush( grad ); newBrush.setMatrix( brush.matrix() ); painter->setBrush( newBrush ); } painter->drawEllipse( QRectF( 0 - maSize.height()/2, 0 - maSize.width()/2, maSize.height(), maSize.width()) ); } break; case MarkerAttributes::MarkerSquare: { QRectF rect( 0 - maSize.width()/2, 0 - maSize.height()/2, maSize.width(), maSize.height() ); painter->drawRect( rect ); break; } case MarkerAttributes::MarkerDiamond: { QVector diamondPoints; QPointF top, left, bottom, right; top = QPointF( 0, 0 - maSize.height()/2 ); left = QPointF( 0 - maSize.width()/2, 0 ); bottom = QPointF( 0, maSize.height()/2 ); right = QPointF( maSize.width()/2, 0 ); diamondPoints << top << left << bottom << right; painter->drawPolygon( diamondPoints ); break; } // both handled on top of the method: case MarkerAttributes::Marker1Pixel: case MarkerAttributes::Marker4Pixels: break; case MarkerAttributes::MarkerRing: { painter->setBrush( Qt::NoBrush ); painter->setPen( PrintingParameters::scalePen( QPen( brush.color() ) ) ); painter->drawEllipse( QRectF( 0 - maSize.height()/2, 0 - maSize.width()/2, maSize.height(), maSize.width()) ); break; } case MarkerAttributes::MarkerCross: { // Note: Markers can have outline, // so just drawing two rects is NOT the solution here! const qreal w02 = maSize.width() * 0.2; const qreal w05 = maSize.width() * 0.5; const qreal h02 = maSize.height()* 0.2; const qreal h05 = maSize.height()* 0.5; QVector crossPoints; QPointF p[12]; p[ 0] = QPointF( -w02, -h05 ); p[ 1] = QPointF( w02, -h05 ); p[ 2] = QPointF( w02, -h02 ); p[ 3] = QPointF( w05, -h02 ); p[ 4] = QPointF( w05, h02 ); p[ 5] = QPointF( w02, h02 ); p[ 6] = QPointF( w02, h05 ); p[ 7] = QPointF( -w02, h05 ); p[ 8] = QPointF( -w02, h02 ); p[ 9] = QPointF( -w05, h02 ); p[10] = QPointF( -w05, -h02 ); p[11] = QPointF( -w02, -h02 ); for ( int i=0; i<12; ++i ) crossPoints << p[i]; crossPoints << p[0]; painter->drawPolygon( crossPoints ); break; } case MarkerAttributes::MarkerFastCross: { QPointF left, right, top, bottom; left = QPointF( -maSize.width()/2, 0 ); right = QPointF( maSize.width()/2, 0 ); top = QPointF( 0, -maSize.height()/2 ); bottom= QPointF( 0, maSize.height()/2 ); painter->setPen( PrintingParameters::scalePen( QPen( brush.color() ) ) ); painter->drawLine( left, right ); painter->drawLine( top, bottom ); break; } case MarkerAttributes::MarkerArrowDown: { QVector arrowPoints; QPointF topLeft, topRight, bottom; topLeft = QPointF( 0 - maSize.width()/2, 0 - maSize.height()/2 ); topRight = QPointF( maSize.width()/2, 0 - maSize.height()/2 ); bottom = QPointF( 0, maSize.height()/2 ); arrowPoints << topLeft << bottom << topRight; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerArrowUp: { QVector arrowPoints; QPointF top, bottomLeft, bottomRight; top = QPointF( 0, 0 - maSize.height()/2 ); bottomLeft = QPointF( 0 - maSize.width()/2, maSize.height()/2 ); bottomRight = QPointF( maSize.width()/2, maSize.height()/2 ); arrowPoints << top << bottomLeft << bottomRight; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerArrowRight: { QVector arrowPoints; QPointF right, topLeft, bottomLeft; right = QPointF( maSize.width()/2, 0 ); topLeft = QPointF( 0 - maSize.width()/2, 0 - maSize.height()/2 ); bottomLeft = QPointF( 0 - maSize.width()/2, maSize.height()/2 ); arrowPoints << topLeft << bottomLeft << right; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerArrowLeft: { QVector arrowPoints; QPointF left, topRight, bottomRight; left = QPointF( 0 - maSize.width()/2, 0 ); topRight = QPointF( maSize.width()/2, 0 - maSize.height()/2 ); bottomRight = QPointF( maSize.width()/2, maSize.height()/2 ); arrowPoints << left << bottomRight << topRight; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerBowTie: case MarkerAttributes::MarkerHourGlass: { QVector points; QPointF topLeft, topRight, bottomLeft, bottomRight; topLeft = QPointF( 0 - maSize.width()/2, 0 - maSize.height()/2); topRight = QPointF( maSize.width()/2, 0 - maSize.height()/2 ); bottomLeft = QPointF( 0 - maSize.width()/2, maSize.height()/2 ); bottomRight = QPointF( maSize.width()/2, maSize.height()/2 ); if ( markerAttributes.markerStyle() == MarkerAttributes::MarkerBowTie) points << topLeft << bottomLeft << topRight << bottomRight; else points << topLeft << bottomRight << bottomLeft << topRight; painter->drawPolygon( points ); break; } case MarkerAttributes::MarkerStar: { const qreal w01 = maSize.width() * 0.1; const qreal w05 = maSize.width() * 0.5; const qreal h01 = maSize.height() * 0.1; const qreal h05 = maSize.height() * 0.5; QVector points; QPointF p1 = QPointF( 0, -h05 ); QPointF p2 = QPointF( -w01, -h01 ); QPointF p3 = QPointF( -w05, 0 ); QPointF p4 = QPointF( -w01, h01 ); QPointF p5 = QPointF( 0, h05 ); QPointF p6 = QPointF( w01, h01 ); QPointF p7 = QPointF( w05, 0 ); QPointF p8 = QPointF( w01, -h01 ); points << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8; painter->drawPolygon( points ); break; } case MarkerAttributes::MarkerX: { const qreal w01 = maSize.width() * 0.1; const qreal w04 = maSize.width() * 0.4; const qreal w05 = maSize.width() * 0.5; const qreal h01 = maSize.height() * 0.1; const qreal h04 = maSize.height() * 0.4; const qreal h05 = maSize.height() * 0.5; QVector crossPoints; QPointF p1 = QPointF( -w04, -h05 ); QPointF p2 = QPointF( -w05, -h04 ); QPointF p3 = QPointF( -w01, 0 ); QPointF p4 = QPointF( -w05, h04 ); QPointF p5 = QPointF( -w04, h05 ); QPointF p6 = QPointF( 0, h01 ); QPointF p7 = QPointF( w04, h05 ); QPointF p8 = QPointF( w05, h04 ); QPointF p9 = QPointF( w01, 0 ); QPointF p10 = QPointF( w05, -h04 ); QPointF p11 = QPointF( w04, -h05 ); QPointF p12 = QPointF( 0, -h01 ); crossPoints << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8 << p9 << p10 << p11 << p12; painter->drawPolygon( crossPoints ); break; } case MarkerAttributes::MarkerAsterisk: { // Note: Markers can have outline, // so just drawing three lines is NOT the solution here! // The idea that we use is to draw 3 lines anyway, but convert their // outlines to QPainterPaths which are then united and filled. const qreal w04 = maSize.width() * 0.4; const qreal h02 = maSize.height() * 0.2; const qreal h05 = maSize.height() * 0.5; //QVector crossPoints; QPointF p1 = QPointF( 0, -h05 ); QPointF p2 = QPointF( -w04, -h02 ); QPointF p3 = QPointF( -w04, h02 ); QPointF p4 = QPointF( 0, h05 ); QPointF p5 = QPointF( w04, h02 ); QPointF p6 = QPointF( w04, -h02 ); QPen pen = painter->pen(); QPainterPathStroker stroker; stroker.setWidth( pen.widthF() ); stroker.setCapStyle( pen.capStyle() ); QPainterPath path; QPainterPath dummyPath; dummyPath.moveTo( p1 ); dummyPath.lineTo( p4 ); path = stroker.createStroke( dummyPath ); dummyPath = QPainterPath(); dummyPath.moveTo( p2 ); dummyPath.lineTo( p5 ); path = path.united( stroker.createStroke( dummyPath ) ); dummyPath = QPainterPath(); dummyPath.moveTo( p3 ); dummyPath.lineTo( p6 ); path = path.united( stroker.createStroke( dummyPath ) ); painter->drawPath( path ); break; } case MarkerAttributes::MarkerHorizontalBar: { const qreal w05 = maSize.width() * 0.5; const qreal h02 = maSize.height()* 0.2; QVector points; QPointF p1 = QPointF( -w05, -h02 ); QPointF p2 = QPointF( -w05, h02 ); QPointF p3 = QPointF( w05, h02 ); QPointF p4 = QPointF( w05, -h02 ); points << p1 << p2 << p3 << p4; painter->drawPolygon( points ); break; } case MarkerAttributes::MarkerVerticalBar: { const qreal w02 = maSize.width() * 0.2; const qreal h05 = maSize.height()* 0.5; QVector points; QPointF p1 = QPointF( -w02, -h05 ); QPointF p2 = QPointF( -w02, h05 ); QPointF p3 = QPointF( w02, h05 ); QPointF p4 = QPointF( w02, -h05 ); points << p1 << p2 << p3 << p4; painter->drawPolygon( points ); break; } case MarkerAttributes::NoMarker: break; case MarkerAttributes::PainterPathMarker: { QPainterPath path = markerAttributes.customMarkerPath(); const QRectF pathBoundingRect = path.boundingRect(); const qreal xScaling = maSize.height() / pathBoundingRect.height(); const qreal yScaling = maSize.width() / pathBoundingRect.width(); const qreal scaling = qMin( xScaling, yScaling ); painter->scale( scaling, scaling ); painter->setPen( PrintingParameters::scalePen( QPen( brush.color() ) ) ); painter->drawPath(path); break; } default: Q_ASSERT_X ( false, "paintMarkers()", "Type item does not match a defined Marker Type." ); } } painter->setPen( oldPen ); } void AbstractDiagram::paintMarkers( QPainter* painter ) { if ( !checkInvariants() ) { return; } const int rowCount = model()->rowCount( rootIndex() ); const int columnCount = model()->columnCount( rootIndex() ); for ( int column = 0; column < columnCount; column += datasetDimension() ) { for ( int row = 0; row < rowCount; ++row ) { QModelIndex index = model()->index( row, column, rootIndex() ); // checked qreal x; qreal y; if ( datasetDimension() == 1 ) { x = row; y = index.data().toReal(); } else { x = index.data().toReal(); y = model()->index( row, column + 1, rootIndex() ).data().toReal(); } paintMarker( painter, index, coordinatePlane()->translate( QPointF( x, y ) ) ); } } } void AbstractDiagram::setPen( const QModelIndex& index, const QPen& pen ) { attributesModel()->setData( conditionallyMapFromSource( index ), QVariant::fromValue( pen ), DatasetPenRole ); emit propertiesChanged(); } void AbstractDiagram::setPen( const QPen& pen ) { attributesModel()->setModelData( QVariant::fromValue( pen ), DatasetPenRole ); emit propertiesChanged(); } void AbstractDiagram::setPen( int dataset, const QPen& pen ) { d->setDatasetAttrs( dataset, QVariant::fromValue( pen ), DatasetPenRole ); emit propertiesChanged(); } QPen AbstractDiagram::pen() const { return attributesModel()->data( DatasetPenRole ).value< QPen >(); } QPen AbstractDiagram::pen( int dataset ) const { const QVariant penSettings( d->datasetAttrs( dataset, DatasetPenRole ) ); if ( penSettings.isValid() ) return penSettings.value< QPen >(); return pen(); } QPen AbstractDiagram::pen( const QModelIndex& index ) const { return attributesModel()->data( conditionallyMapFromSource( index ), DatasetPenRole ).value< QPen >(); } void AbstractDiagram::setBrush( const QModelIndex& index, const QBrush& brush ) { attributesModel()->setData( conditionallyMapFromSource( index ), QVariant::fromValue( brush ), DatasetBrushRole ); emit propertiesChanged(); } void AbstractDiagram::setBrush( const QBrush& brush ) { attributesModel()->setModelData( QVariant::fromValue( brush ), DatasetBrushRole ); emit propertiesChanged(); } void AbstractDiagram::setBrush( int dataset, const QBrush& brush ) { d->setDatasetAttrs( dataset, QVariant::fromValue( brush ), DatasetBrushRole ); emit propertiesChanged(); } QBrush AbstractDiagram::brush() const { return attributesModel()->data( DatasetBrushRole ).value< QBrush >(); } QBrush AbstractDiagram::brush( int dataset ) const { const QVariant brushSettings( d->datasetAttrs( dataset, DatasetBrushRole ) ); if ( brushSettings.isValid() ) return brushSettings.value< QBrush >(); return brush(); } QBrush AbstractDiagram::brush( const QModelIndex& index ) const { return attributesModel()->data( conditionallyMapFromSource( index ), DatasetBrushRole ).value< QBrush >(); } /* * Sets the unit prefix for one value * @param prefix the prefix to be set * @param column the value using that prefix * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitPrefix( const QString& prefix, int column, Qt::Orientation orientation ) { d->unitPrefixMap[ column ][ orientation ]= prefix; } /* * Sets the unit prefix for all values * @param prefix the prefix to be set * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitPrefix( const QString& prefix, Qt::Orientation orientation ) { d->unitPrefix[ orientation ] = prefix; } /* * Sets the unit suffix for one value * @param suffix the suffix to be set * @param column the value using that suffix * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitSuffix( const QString& suffix, int column, Qt::Orientation orientation ) { d->unitSuffixMap[ column ][ orientation ]= suffix; } /* * Sets the unit suffix for all values * @param suffix the suffix to be set * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitSuffix( const QString& suffix, Qt::Orientation orientation ) { d->unitSuffix[ orientation ] = suffix; } /* * Returns the unit prefix for a special value * @param column the value which's prefix is requested * @param orientation the orientation of the axis * @param fallback if true, the global prefix is return when no specific one is set for that value * @return the unit prefix */ QString AbstractDiagram::unitPrefix( int column, Qt::Orientation orientation, bool fallback ) const { if ( !fallback || d->unitPrefixMap[ column ].contains( orientation ) ) return d->unitPrefixMap[ column ][ orientation ]; return d->unitPrefix[ orientation ]; } /* Returns the global unit prefix * @param orientation the orientation of the axis * @return the unit prefix */ QString AbstractDiagram::unitPrefix( Qt::Orientation orientation ) const { return d->unitPrefix[ orientation ]; } /* * Returns the unit suffix for a special value * @param column the value which's suffix is requested * @param orientation the orientation of the axis * @param fallback if true, the global suffix is return when no specific one is set for that value * @return the unit suffix */ QString AbstractDiagram::unitSuffix( int column, Qt::Orientation orientation, bool fallback ) const { if ( !fallback || d->unitSuffixMap[ column ].contains( orientation ) ) return d->unitSuffixMap[ column ][ orientation ]; return d->unitSuffix[ orientation ]; } /* Returns the global unit suffix * @param orientation the orientation of the axis * @return the unit siffix */ QString AbstractDiagram::unitSuffix( Qt::Orientation orientation ) const { return d->unitSuffix[ orientation ]; } // implement QAbstractItemView: QRect AbstractDiagram::visualRect( const QModelIndex &index ) const { return d->reverseMapper.boundingRect( index.row(), index.column() ).toRect(); } void AbstractDiagram::scrollTo(const QModelIndex &, ScrollHint ) {} // indexAt ... down below QModelIndex AbstractDiagram::moveCursor(CursorAction, Qt::KeyboardModifiers ) { return QModelIndex(); } int AbstractDiagram::horizontalOffset() const { return 0; } int AbstractDiagram::verticalOffset() const { return 0; } bool AbstractDiagram::isIndexHidden(const QModelIndex &) const { return true; } void AbstractDiagram::setSelection(const QRect& rect , QItemSelectionModel::SelectionFlags command ) { const QModelIndexList indexes = d->indexesIn( rect ); QItemSelection selection; Q_FOREACH( const QModelIndex& index, indexes ) { selection.append( QItemSelectionRange( index ) ); } selectionModel()->select( selection, command ); } QRegion AbstractDiagram::visualRegionForSelection(const QItemSelection &selection) const { QPolygonF polygon; Q_FOREACH( const QModelIndex& index, selection.indexes() ) { polygon << d->reverseMapper.polygon(index.row(), index.column()); } return polygon.isEmpty() ? QRegion() : QRegion( polygon.toPolygon() ); } QRegion AbstractDiagram::visualRegion(const QModelIndex &index) const { QPolygonF polygon = d->reverseMapper.polygon(index.row(), index.column()); return polygon.isEmpty() ? QRegion() : QRegion( polygon.toPolygon() ); } void KChart::AbstractDiagram::useDefaultColors( ) { d->attributesModel->setPaletteType( AttributesModel::PaletteTypeDefault ); } void KChart::AbstractDiagram::useSubduedColors( ) { d->attributesModel->setPaletteType( AttributesModel::PaletteTypeSubdued ); } void KChart::AbstractDiagram::useRainbowColors( ) { d->attributesModel->setPaletteType( AttributesModel::PaletteTypeRainbow ); } QStringList AbstractDiagram::itemRowLabels() const { QStringList ret; if ( model() ) { //qDebug() << "AbstractDiagram::itemRowLabels(): " << attributesModel()->rowCount(attributesModelRootIndex()) << "entries"; const int rowCount = attributesModel()->rowCount(attributesModelRootIndex()); for ( int i = 0; i < rowCount; ++i ) { //qDebug() << "item row label: " << attributesModel()->headerData( i, Qt::Vertical, Qt::DisplayRole ).toString(); ret << unitPrefix( i, Qt::Horizontal, true ) + attributesModel()->headerData( i, Qt::Vertical, Qt::DisplayRole ).toString() + unitSuffix( i, Qt::Horizontal, true ); } } return ret; } QStringList AbstractDiagram::datasetLabels() const { QStringList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << d->datasetAttrs( i, Qt::DisplayRole ).toString(); } return ret; } QList AbstractDiagram::datasetBrushes() const { QList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << brush( i ); } return ret; } QList AbstractDiagram::datasetPens() const { QList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << pen( i ); } return ret; } QList AbstractDiagram::datasetMarkers() const { QList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << dataValueAttributes( i ).markerAttributes(); } return ret; } bool AbstractDiagram::checkInvariants( bool justReturnTheStatus ) const { if ( ! justReturnTheStatus ) { Q_ASSERT_X ( model(), "AbstractDiagram::checkInvariants()", "There is no usable model set, for the diagram." ); Q_ASSERT_X ( coordinatePlane(), "AbstractDiagram::checkInvariants()", "There is no usable coordinate plane set, for the diagram." ); } return model() && coordinatePlane(); } int AbstractDiagram::datasetDimension( ) const { return d->datasetDimension; } void AbstractDiagram::setDatasetDimension( int dimension ) { Q_UNUSED( dimension ); qDebug() << "Setting the dataset dimension using AbstractDiagram::setDatasetDimension is " "obsolete. Use the specific diagram types instead."; } void AbstractDiagram::setDatasetDimensionInternal( int dimension ) { Q_ASSERT( dimension != 0 ); if ( d->datasetDimension == dimension ) { return; } d->datasetDimension = dimension; d->attributesModel->setDatasetDimension( dimension ); setDataBoundariesDirty(); emit layoutChanged( this ); } qreal AbstractDiagram::valueForCell( int row, int column ) const { if ( !d->attributesModel->hasIndex( row, column, attributesModelRootIndex() ) ) { qWarning() << "AbstractDiagram::valueForCell(): Requesting value for invalid index!"; return std::numeric_limits::quiet_NaN(); } return d->attributesModel->data( d->attributesModel->index( row, column, attributesModelRootIndex() ) ).toReal(); // checked } void AbstractDiagram::update() const { if ( d->plane ) { d->plane->update(); } } QModelIndex AbstractDiagram::indexAt( const QPoint& point ) const { return d->indexAt( point ); } QModelIndexList AbstractDiagram::indexesAt( const QPoint& point ) const { return d->indexesAt( point ); } QModelIndexList AbstractDiagram::indexesIn( const QRect& rect ) const { return d->indexesIn( rect ); } diff --git a/src/KChart/KChartAbstractDiagram.h b/src/KChart/KChartAbstractDiagram.h index e2a9162..1152588 100644 --- a/src/KChart/KChartAbstractDiagram.h +++ b/src/KChart/KChartAbstractDiagram.h @@ -1,735 +1,740 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTABSTRACTDIAGRAM_H #define KCHARTABSTRACTDIAGRAM_H #include #include #include #include "KChartGlobal.h" #include "KChartMarkerAttributes.h" #include "KChartAttributesModel.h" namespace KChart { class AbstractCoordinatePlane; class AttributesModel; class DataValueAttributes; class PaintContext; /** * @brief AbstractDiagram defines the interface for diagram classes * * AbstractDiagram is the base class for diagram classes ("chart types"). * * It defines the interface, that needs to be implemented for the diagram, * to function within the KChart framework. It extends Interview's * QAbstractItemView. */ class KCHART_EXPORT AbstractDiagram : public QAbstractItemView { Q_OBJECT Q_DISABLE_COPY( AbstractDiagram ) KCHART_DECLARE_PRIVATE_BASE_POLYMORPHIC( AbstractDiagram ) friend class AbstractCoordinatePlane; friend class CartesianCoordinatePlane; friend class PolarCoordinatePlane; protected: explicit inline AbstractDiagram( Private *p, QWidget* parent, AbstractCoordinatePlane* plane ); explicit AbstractDiagram ( QWidget* parent = nullptr, AbstractCoordinatePlane* plane = nullptr ); public: virtual ~AbstractDiagram(); /** * Returns true if both diagrams have the same settings. */ bool compare( const AbstractDiagram* other ) const; /** * @brief Return the bottom left and top right data point, that the * diagram will display (unless the grid adjusts these values). * * This method returns a cached result of calculations done by * calculateDataBoundaries. * Classes derived from AbstractDiagram must implement the * calculateDataBoundaries function, to specify their own * way of calculating the data boundaries. * If derived classes want to force recalculation of the * data boundaries, they can call setDataBoundariesDirty() * * Returned value is in diagram coordinates. */ const QPair dataBoundaries() const; // protected: // FIXME: why should that be private? (Mirko) /** * Draw the diagram contents to the rectangle and painter, that are * passed in as part of the paint context. * * @param paintContext All information needed for painting. */ virtual void paint ( PaintContext* paintContext ) = 0; /** * Called by the widget's sizeEvent. Adjust all internal structures, * that are calculated, dependending on the size of the widget. * * @param area */ virtual void resize ( const QSizeF& area ); /** Associate a model with the diagram. */ void setModel ( QAbstractItemModel * model ) override; /** Associate a seleection model with the diagrom. */ void setSelectionModel( QItemSelectionModel* selectionModel ) override; /** * Associate an AttributesModel with this diagram. Note that * the diagram does _not_ take ownership of the AttributesModel. * This should thus only be used with AttributesModels that * have been explicitly created by the user, and are owned * by her. Setting an AttributesModel that is internal to * another diagram is an error. * * Correct: * * \code * AttributesModel *am = new AttributesModel( model, 0 ); * diagram1->setAttributesModel( am ); * diagram2->setAttributesModel( am ); * * \endcode * * Wrong: * * \code * * diagram1->setAttributesModel( diagram2->attributesModel() ); * * \endcode * * @param model The AttributesModel to use for this diagram. * @see AttributesModel, usesExternalAttributesModel */ virtual void setAttributesModel( AttributesModel* model ); /** * Returns whether the diagram is using its own built-in attributes model * or an attributes model that was set via setAttributesModel. * * @see setAttributesModel */ virtual bool usesExternalAttributesModel() const; /** * Returns the AttributesModel, that is used by this diagram. * By default each diagram owns its own AttributesModel, which * should never be deleted. Only if a user-supplied AttributesModel * has been set does the pointer returned here not belong to the * diagram. * * @return The AttributesModel associated with the diagram. * @see setAttributesModel */ virtual AttributesModel* attributesModel() const; /** Set the root index in the model, where the diagram starts * referencing data for display. */ void setRootIndex ( const QModelIndex& idx ) override; /** \reimpl */ QRect visualRect(const QModelIndex &index) const override; /** \reimpl */ void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; /** \reimpl */ QModelIndex indexAt(const QPoint &point) const override; /** \reimpl */ QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; /** \reimpl */ int horizontalOffset() const override; /** \reimpl */ int verticalOffset() const override; /** \reimpl */ bool isIndexHidden(const QModelIndex &index) const override; /** \reimpl */ void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) override; /** \reimpl */ QRegion visualRegionForSelection(const QItemSelection &selection) const override; virtual QRegion visualRegion(const QModelIndex &index) const; /** \reimpl */ void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; /** \reimpl */ void doItemsLayout() override; /** * The coordinate plane associated with the diagram. This determines * how coordinates in value space are mapped into pixel space. By default * this is a CartesianCoordinatePlane. * @return The coordinate plane associated with the diagram. */ AbstractCoordinatePlane* coordinatePlane() const; /** * Set the coordinate plane associated with the diagram. This determines * how coordinates in value space are mapped into pixel space. The chart * takes ownership. * @return The coordinate plane associated with the diagram. */ virtual void setCoordinatePlane( AbstractCoordinatePlane* plane ); /** * Hide (or unhide, resp.) a data cell. * * \note Hidden data are still taken into account by the coordinate plane, * so neither the grid nor your axes' ranges will change, when you hide data. * For totally removing data from KChart's view you can use another approach: * e.g. you could define a proxy model on top of your data model, and register * the proxy model calling setModel() instead of registering your real data model. * * @param index The datapoint to set the hidden status for. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @param hidden The hidden status to set. */ void setHidden( const QModelIndex & index, bool hidden ); /** * Hide (or unhide, resp.) a dataset. * * \note Hidden data are still taken into account by the coordinate plane, * so neither the grid nor your axes' ranges will change, when you hide data. * For totally removing data from KChart's view you can use another approach: * e.g. you could define a proxy model on top of your data model, and register * the proxy model calling setModel() instead of registering your real data model. * * @param dataset The dataset to set the hidden status for. * @param hidden The hidden status to set. */ void setHidden( int dataset, bool hidden ); /** * Hide (or unhide, resp.) all datapoints in the model. * * \note Hidden data are still taken into account by the coordinate plane, * so neither the grid nor your axes' ranges will change, when you hide data. * For totally removing data from KChart's view you can use another approach: * e.g. you could define a proxy model on top of your data model, and register * the proxy model calling setModel() instead of registering your real data model. * * @param hidden The hidden status to set. */ void setHidden( bool hidden ); /** * Retrieve the hidden status specified globally. This will fall * back automatically to the default settings ( = not hidden), if there * are no specific settings. * @return The global hidden status. */ bool isHidden() const; /** * Retrieve the hidden status for the given dataset. This will fall * back automatically to what was set at diagram level, if there * are no dataset specific settings. * @param dataset The dataset to retrieve the hidden status for. * @return The hidden status for the given dataset. */ bool isHidden( int dataset ) const; /** * Retrieve the hidden status for the given index. This will fall * back automatically to what was set at dataset or diagram level, if there * are no datapoint specific settings. * @param index The datapoint to retrieve the hidden status for. * @return The hidden status for the given index. */ bool isHidden( const QModelIndex & index ) const; /** * Set the DataValueAttributes for the given index. * @param index The datapoint to set the attributes for. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @param a The attributes to set. */ void setDataValueAttributes( const QModelIndex & index, const DataValueAttributes & a ); /** * Set the DataValueAttributes for the given dataset. * @param dataset The dataset to set the attributes for. * @param a The attributes to set. */ void setDataValueAttributes( int dataset, const DataValueAttributes & a ); /** * Set the DataValueAttributes for all datapoints in the model. * @param a The attributes to set. */ void setDataValueAttributes( const DataValueAttributes & a ); /** * Retrieve the DataValueAttributes specified globally. This will fall * back automatically to the default settings, if there * are no specific settings. * @return The global DataValueAttributes. */ DataValueAttributes dataValueAttributes() const; /** * Retrieve the DataValueAttributes for the given dataset. This will fall * back automatically to what was set at model level, if there * are no dataset specific settings. * @param dataset The dataset to retrieve the attributes for. * @return The DataValueAttributes for the given dataset. */ DataValueAttributes dataValueAttributes( int dataset ) const; /** * Retrieve the DataValueAttributes for the given index. This will fall * back automatically to what was set at dataset or model level, if there * are no datapoint specific settings. * @param index The datapoint to retrieve the attributes for. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @return The DataValueAttributes for the given index. */ DataValueAttributes dataValueAttributes( const QModelIndex & index ) const; /** * Set the pen to be used, for painting the datapoint at the given index. * @param index The datapoint's index in the model. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @param pen The pen to use. */ void setPen( const QModelIndex& index, const QPen& pen ); /** * Set the pen to be used, for painting the given dataset. * @param dataset The dataset to set the pen for. * @param pen The pen to use. */ void setPen( int dataset, const QPen& pen ); /** * Set the pen to be used, for painting all datasets in the model. * @param pen The pen to use. */ void setPen( const QPen& pen ); /** * Retrieve the pen to be used for painting datapoints globally. This will fall * back automatically to the default settings, if there * are no specific settings. * @return The pen to use for painting. */ QPen pen() const; /** * Retrieve the pen to be used for the given dataset. This will fall * back automatically to what was set at model level, if there * are no dataset specific settings. * @param dataset The dataset to retrieve the pen for. * @return The pen to use for painting. */ QPen pen( int dataset ) const; /** * Retrieve the pen to be used, for painting the datapoint at the given * index in the model. * @param index The index of the datapoint in the model. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @return The pen to use for painting. */ QPen pen( const QModelIndex& index ) const; /** * Set the brush to be used, for painting the datapoint at the given index. * @param index The datapoint's index in the model. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @param brush The brush to use. */ void setBrush( const QModelIndex& index, const QBrush& brush); /** * Set the brush to be used, for painting the given dataset. * @param dataset The dataset to set the brush for. * @param brush The brush to use. */ void setBrush( int dataset, const QBrush& brush ); /** * Set the brush to be used, for painting all datasets in the model. * @param brush The brush to use. */ void setBrush( const QBrush& brush); /** * Retrieve the brush to be used for painting datapoints globally. This will fall * back automatically to the default settings, if there * are no specific settings. * @return The brush to use for painting. */ QBrush brush() const; /** * Retrieve the brush to be used for the given dataset. This will fall * back automatically to what was set at model level, if there * are no dataset specific settings. * @param dataset The dataset to retrieve the brush for. * @return The brush to use for painting. */ QBrush brush( int dataset ) const; /** * Retrieve the brush to be used, for painting the datapoint at the given * index in the model. * @param index The index of the datapoint in the model. With a dataset dimension * of two, this is the index of the key of each key/value pair. * @return The brush to use for painting. */ QBrush brush( const QModelIndex& index ) const; /** * Set the unit prefix to be used on axes for one specific column. * @param prefix The prefix to be used. * @param column The column which should be set. * @param orientation The orientation of the axis to use. */ void setUnitPrefix( const QString& prefix, int column, Qt::Orientation orientation ); /** * Set the unit prefix to be used on axes for all columns. * @param prefix The prefix to be used. * @param orientation The orientation of the axis to use. */ void setUnitPrefix( const QString& prefix, Qt::Orientation orientation ); /** * Set the unit prefix to be used on axes for one specific column. * @param suffix The suffix to be used. * @param column The column which should be set. * @param orientation The orientation of the axis to use. */ void setUnitSuffix( const QString& suffix, int column, Qt::Orientation orientation ); /** * Set the unit prefix to be used on axes for all columns. * @param suffix The suffix to be used. * @param orientation The orientation of the axis to use. */ void setUnitSuffix( const QString& suffix, Qt::Orientation orientation ); /** * Retrieves the axis unit prefix for a specific column. * @param column The column whose prefix should be retrieved. * @param orientation The orientation of the axis. * @param fallback If true, the prefix for all columns is returned, when * none is set for the selected column. * @return The axis unit prefix. */ QString unitPrefix( int column, Qt::Orientation orientation, bool fallback = false ) const; /** * Retrieves the axis unit prefix. * @param orientation The orientation of the axis. * @return The axis unit prefix. */ QString unitPrefix( Qt::Orientation orientation ) const; /** * Retrieves the axis unit suffix for a specific column. * @param column The column whose prefix should be retrieved. * @param orientation The orientation of the axis. * @param fallback If true, the suffix for all columns is returned, when * none is set for the selected column. * @return The axis unit suffix. */ QString unitSuffix( int column, Qt::Orientation orientation, bool fallback = false ) const; /** * Retrieves the axis unit suffix. * @param orientation The orientation of the axis. * @return The axis unit suffix. */ QString unitSuffix( Qt::Orientation orientation ) const; /** * Set whether data value labels are allowed to overlap. * @param allow True means that overlapping labels are allowed. */ void setAllowOverlappingDataValueTexts( bool allow ); /** * @return Whether data value labels are allowed to overlap. */ bool allowOverlappingDataValueTexts() const; /** * Set whether anti-aliasing is to be used while rendering * this diagram. * @param enabled True means that AA is enabled. */ void setAntiAliasing( bool enabled ); /** * @return Whether anti-aliasing is to be used for rendering * this diagram. */ bool antiAliasing() const; /** * Set the palette to be used, for painting datasets to the default * palette. * @see KChart::Palette. * FIXME: fold into one usePalette (KChart::Palette&) method */ void useDefaultColors(); /** * Set the palette to be used, for painting datasets to the rainbow * palette. * @see KChart::Palette. */ void useRainbowColors(); /** * Set the palette to be used, for painting datasets to the subdued * palette. * @see KChart::Palette. */ void useSubduedColors(); /** * The set of item row labels currently displayed, for use in Abscissa axes, etc. * @return The set of item row labels currently displayed. */ QStringList itemRowLabels() const; /** * The set of dataset labels currently displayed, for use in legends, etc. * @return The set of dataset labels currently displayed. */ QStringList datasetLabels() const; /** * The set of dataset brushes currently used, for use in legends, etc. * * @note Cell-level override brushes, if set, take precedence over the * dataset values, so you might need to check these too, in order to find * the brush, that is used for a single cell. * * @return The current set of dataset brushes. */ QList datasetBrushes() const; /** * The set of dataset pens currently used, for use in legends, etc. * * @note Cell-level override pens, if set, take precedence over the * dataset values, so you might need to check these too, in order to find * the pens, that is used for a single cell. * * @return The current set of dataset pens. */ QList datasetPens() const; /** * The set of dataset markers currently used, for use in legends, etc. * * @note Cell-level override markers, if set, take precedence over the * dataset values, so you might need to check these too, in order to find * the marker, that is shown for a single cell. * * @return The current set of dataset brushes. */ QList datasetMarkers() const; /** * \deprecated * * \brief Deprecated method that turns the percent mode of this diagram on or off. * * This method is deprecated. Use the setType() method of a supporting diagram implementation * instead, e.g. BarDiagram::setType(). * * \see percentMode */ void setPercentMode( bool percent ); /** * \brief Returns whether this diagram is drawn in percent mode. * * If true, all data points in the same column of a diagram will * be be drawn at the same X coordinate and stacked up so that the distance from the * last data point (or the zero line) to a data point P is always the ratio of (Y-Value of P)/ * (sum of all Y-Values in same column as P) relative to the diagrams height * (or width, if abscissa and ordinate are swapped). * * Note that this property is not applicable to all diagram types. */ bool percentMode() const; virtual void paintMarker( QPainter* painter, const MarkerAttributes& markerAttributes, const QBrush& brush, const QPen&, const QPointF& point, const QSizeF& size ); /** * The dataset dimension of a diagram determines how many value dimensions * it expects each datapoint to have. * For each dimension and data series it will expect one column of values in the model. * If the dimension is 1, automatic values will be used for X. * * For example, a diagram with the default dimension of 1 will have one column * per data series (the Y values) and will use automatic values for X * (1, 2, 3, ... n). * If the dimension is 2, the diagram will use the first, (and the third, * fifth, etc) columns as X values, and the second, (and the fourth, sixth, * etc) column as Y values. * @return The dataset dimension of the diagram. */ int datasetDimension() const; /** * \deprecated * * Sets the dataset dimension of the diagram. Using this method * is deprecated. Use the specific diagram types instead. */ void setDatasetDimension( int dimension ); protected: void setDatasetDimensionInternal( int dimension ); public: void update() const; void paintMarker( QPainter* painter, const DataValueAttributes& a, const QModelIndex& index, const QPointF& pos ); void paintMarker( QPainter* painter, const QModelIndex& index, const QPointF& pos ); void paintDataValueText( QPainter* painter, const QModelIndex& index, const QPointF& pos, qreal value ); // reverse mapping: /** This method is added alongside with indexAt from QAIM, since in KChart multiple indexes can be displayed at the same spot. */ QModelIndexList indexesAt( const QPoint& point ) const; QModelIndexList indexesIn( const QRect& rect ) const; protected: virtual bool checkInvariants( bool justReturnTheStatus=false ) const; virtual const QPair calculateDataBoundaries() const = 0; protected Q_SLOTS: void setDataBoundariesDirty() const; protected: /** * \deprecated * This method is deprecated and provided for backward-compatibility only. * Your own diagram classes should call * d->paintDataValueTextsAndMarkers() instead * which also is taking care for showing your cell-specific comments, if any, */ virtual void paintDataValueTexts( QPainter* painter ); /** * \deprecated * This method is deprecated and provided for backward-compatibility only. * Your own diagram classes should call * d->paintDataValueTextsAndMarkers() instead * which also is taking care for showing your cell-specific comments, if any, */ virtual void paintMarkers( QPainter* painter ); + + /*! \internal */ void setAttributesModelRootIndex( const QModelIndex& ); + + /*! returns a QModelIndex pointing into the AttributesModel that corresponds to the + root index of the diagram. */ QModelIndex attributesModelRootIndex() const; /** * Helper method, retrieving the data value (DisplayRole) for a given row and column * @param row The row to query. * @param column The column to query. * @return The value of the display role at the given row and column as a qreal. * @deprecated */ qreal valueForCell( int row, int column ) const; Q_SIGNALS: /** Diagrams are supposed to emit this signal, when the layout of one of their element changes. Layouts can change, for example, when axes are added or removed, or when the configuration was changed in a way that the axes or the diagram itself are displayed in a different geometry. Changes in the diagrams coordinate system also result in the layoutChanged() signal being emitted. */ void layoutChanged( AbstractDiagram* ); /** * This signal is emitted when this diagram is being destroyed, but before all the * data, i.e. the attributes model, is invalidated. */ void aboutToBeDestroyed(); /** This signal is emitted when either the model or the AttributesModel is replaced. */ void modelsChanged(); /** This signal is emitted just before the new attributes model is connected internally. It gives you a chance to connect to its signals first or perform other setup work. */ void attributesModelAboutToChange( AttributesModel* newModel, AttributesModel* oldModel ); /** This signal is emitted, when the model data is changed. */ void modelDataChanged(); /** This signal is emitted, when the hidden status of at least one data cell was (un)set. */ void dataHidden(); /** Emitted upon change of a property of the Diagram. */ void propertiesChanged(); /** Emitted upon change of a data boundary */ void boundariesChanged(); /** Emitted upon change of the view coordinate system */ void viewportCoordinateSystemChanged(); private: QModelIndex conditionallyMapFromSource( const QModelIndex & sourceIndex ) const; }; typedef QList AbstractDiagramList; typedef QList ConstAbstractDiagramList; /** * @brief Internally used class just adding a special constructor used by AbstractDiagram */ class PrivateAttributesModel : public AttributesModel { Q_OBJECT public: explicit PrivateAttributesModel( QAbstractItemModel* model, QObject * parent = nullptr ) : AttributesModel(model,parent) {} }; } #endif diff --git a/src/KChart/KChartAbstractProxyModel.cpp b/src/KChart/KChartAbstractProxyModel.cpp index f43a35a..96c4cf9 100644 --- a/src/KChart/KChartAbstractProxyModel.cpp +++ b/src/KChart/KChartAbstractProxyModel.cpp @@ -1,96 +1,94 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAbstractProxyModel.h" #include "KChartMath_p.h" #include #ifdef __GNUC__ #if __GNUC__ > 3 #define MAY_ALIAS __attribute__((__may_alias__)) #endif #else #define MAY_ALIAS #endif namespace KChart { - /** This is basically KDAbstractProxyModel, but only the - bits that we really need from it */ -AbstractProxyModel::AbstractProxyModel(QObject* parent) + AbstractProxyModel::AbstractProxyModel(QObject* parent) : QAbstractProxyModel(parent) {} // Allows access to QModelIndex's private data via type punning and a compatible data layout. // Due to inlining in Qt and no d-pointer, it is safe to assume that the layout won't change except // between major Qt versions. As it happens, the layout is the same in Qt4 and Qt5. // The only change is void * -> quintptr. struct MAY_ALIAS KDPrivateModelIndex { int r, c; void *p; const QAbstractItemModel *m; }; QModelIndex AbstractProxyModel::mapFromSource( const QModelIndex & sourceIndex ) const { if ( !sourceIndex.isValid() ) return QModelIndex(); //qDebug() << "sourceIndex.model()="<. */ #include "KChartGridAttributes.h" #include "KChartMath_p.h" #include #include #define d d_func() using namespace KChart; class Q_DECL_HIDDEN GridAttributes::Private { friend class GridAttributes; public: Private(); private: bool visible; KChartEnums::GranularitySequence sequence; bool linesOnAnnotations; qreal stepWidth; qreal subStepWidth; bool adjustLower; bool adjustUpper; QPen pen; bool subVisible; QPen subPen; bool outerVisible; QPen zeroPen; }; GridAttributes::Private::Private() : visible( true ), sequence( KChartEnums::GranularitySequence_10_20 ), linesOnAnnotations( false ), stepWidth( 0.0 ), subStepWidth( 0.0 ), adjustLower( true ), adjustUpper( true ), pen( QColor(0xa0, 0xa0, 0xa0 ) ), subVisible( true ), subPen( QColor(0xd0, 0xd0, 0xd0 ) ), outerVisible( true ), zeroPen( QColor( 0x00, 0x00, 0x80 ) ) { pen.setCapStyle( Qt::FlatCap ); subPen.setCapStyle( Qt::FlatCap ); zeroPen.setCapStyle( Qt::FlatCap ); } GridAttributes::GridAttributes() : _d( new Private() ) { // this bloc left empty intentionally } GridAttributes::GridAttributes( const GridAttributes& r ) : _d( new Private( *r.d ) ) { } GridAttributes & GridAttributes::operator=( const GridAttributes& r ) { if ( this == &r ) return *this; *d = *r.d; return *this; } GridAttributes::~GridAttributes() { delete _d; _d = nullptr; } bool GridAttributes::operator==( const GridAttributes& r ) const { return isGridVisible() == r.isGridVisible() && gridGranularitySequence() == r.gridGranularitySequence() && linesOnAnnotations() == r.linesOnAnnotations() && adjustLowerBoundToGrid() == r.adjustLowerBoundToGrid() && adjustUpperBoundToGrid() == r.adjustUpperBoundToGrid() && gridPen() == r.gridPen() && isSubGridVisible() == r.isSubGridVisible() && subGridPen() == r.subGridPen() && isOuterLinesVisible() == r.isOuterLinesVisible() && zeroLinePen() == r.zeroLinePen(); } void GridAttributes::setGridVisible( bool visible ) { d->visible = visible; } bool GridAttributes::isGridVisible() const { return d->visible; } void GridAttributes::setLinesOnAnnotations( bool b ) { d->linesOnAnnotations = b; } bool GridAttributes::linesOnAnnotations() const { return d->linesOnAnnotations; } -/** - * Specifies the step width to be used for calculating - * the grid lines. - * - * \note Step with can be set for Linear axis calculation mode only, - * there is no way to specify a step width for Logarithmic axes. - * - * By default the GridAttributes class does not use a fixed step width, - * but it uses KChartEnums::GranularitySequence_10_20. - * - * \param stepWidth the step width to be used. - * If this parameter is omitted (or set to Zero, resp.) - * the automatic step width calculation will be done, - * using the granularity sequence specified. - * This is the default. - * - * \sa gridStepWidth, setGranularitySequence - */ void GridAttributes::setGridStepWidth( qreal stepWidth ) { d->stepWidth = stepWidth; } -/** - * Returns the step width to be used for calculating - * the grid lines. - * - * \sa setGridStepWidth - */ qreal GridAttributes::gridStepWidth() const { return d->stepWidth; } -/** - * Specifies the sub-step width to be used for calculating - * the grid sub-lines. - * - * - * \param subStepWidth the sub-step width to be used. - * If this parameter is omitted (or set to Zero, resp.) - * the automatic calculation will be done, using the - * granularity sequence specified. - * This is the default. - * - * \sa gridSubStepWidth - */ void GridAttributes::setGridSubStepWidth( qreal subStepWidth ) { d->subStepWidth = subStepWidth; } -/** - * Returns the sub-step width to be used for calculating - * the sub-grid lines. - * - * \sa setGridStepWidth - */ qreal GridAttributes::gridSubStepWidth() const { return d->subStepWidth; } -/** - * Specifies the granularity sequence to be used for calculating - * the grid lines. - * - * By default the GridAttributes class uses KChartEnums::GranularitySequence_10_20. - * - * \note Granularity can be set for Linear axis calculation mode only, - * there is no way to specify a step width for Logarithmic axes. - * - * \note The sequence specified by this method is ignored, if - * a fixed step width was specified via setStepWidth. - * - * \param sequence one of the sequences declared in - * KChartEnums::GranularitySequence. - * - * \sa gridGranularitySequence, setStepWidth - */ void GridAttributes::setGridGranularitySequence( KChartEnums::GranularitySequence sequence ) { d->sequence = sequence; } -/** - * Returns the granularity sequence to be used for calculating - * the grid lines. - * - * \sa setGridGranularitySequence - */ KChartEnums::GranularitySequence GridAttributes::gridGranularitySequence() const { return d->sequence; } void GridAttributes::setAdjustBoundsToGrid( bool adjustLower, bool adjustUpper ) { d->adjustLower = adjustLower; d->adjustUpper = adjustUpper; } bool GridAttributes::adjustLowerBoundToGrid() const { return d->adjustLower; } bool GridAttributes::adjustUpperBoundToGrid() const { return d->adjustUpper; } void GridAttributes::setGridPen( const QPen & pen ) { d->pen = pen; d->pen.setCapStyle( Qt::FlatCap ); } QPen GridAttributes::gridPen() const { return d->pen; } void GridAttributes::setSubGridVisible( bool visible ) { d->subVisible = visible; } bool GridAttributes::isSubGridVisible() const { return d->subVisible; } void GridAttributes::setSubGridPen( const QPen & pen ) { d->subPen = pen; d->subPen.setCapStyle( Qt::FlatCap ); } QPen GridAttributes::subGridPen() const { return d->subPen; } void GridAttributes::setOuterLinesVisible( bool visible ) { d->outerVisible = visible; } bool GridAttributes::isOuterLinesVisible() const { return d->outerVisible; } void GridAttributes::setZeroLinePen( const QPen & pen ) { d->zeroPen = pen; d->zeroPen.setCapStyle( Qt::FlatCap ); } QPen GridAttributes::zeroLinePen() const { return d->zeroPen; } #if !defined(QT_NO_DEBUG_STREAM) QDebug operator<<(QDebug dbg, const KChart::GridAttributes& a) { dbg << "KChart::GridAttributes(" << "visible="<. */ #ifndef KCHARTGRIDATTRIBUTES_H #define KCHARTGRIDATTRIBUTES_H #include #include "KChartGlobal.h" #include "KChartEnums.h" QT_BEGIN_NAMESPACE class QPen; QT_END_NAMESPACE namespace KChart { /** * @brief A set of attributes controlling the appearance of grids */ class KCHART_EXPORT GridAttributes { public: GridAttributes(); GridAttributes( const GridAttributes& ); GridAttributes &operator= ( const GridAttributes& ); ~GridAttributes(); void setGridVisible( bool visible ); bool isGridVisible() const; /** * When this is enabled, grid lines are drawn only where axis annotations are. * Otherwise annotations are disregarded as far as the grid is concerned. * * The default is false. */ void setLinesOnAnnotations( bool ); bool linesOnAnnotations() const; + + /** + * Specifies the step width to be used for calculating + * the grid lines. + * + * \note Step with can be set for Linear axis calculation mode only, + * there is no way to specify a step width for Logarithmic axes. + * + * By default the GridAttributes class does not use a fixed step width, + * but it uses KChartEnums::GranularitySequence_10_20. + * + * \param stepWidth the step width to be used. + * If this parameter is omitted (or set to Zero, resp.) + * the automatic step width calculation will be done, + * using the granularity sequence specified. + * This is the default. + * + * \sa gridStepWidth, setGranularitySequence + */ void setGridStepWidth( qreal stepWidth = 0.0 ); + + /** + * Returns the step width to be used for calculating + * the grid lines. + * + * \sa setGridStepWidth + */ qreal gridStepWidth() const; + + /** + * Specifies the sub-step width to be used for calculating + * the grid sub-lines. + * + * + * \param subStepWidth the sub-step width to be used. + * If this parameter is omitted (or set to Zero, resp.) + * the automatic calculation will be done, using the + * granularity sequence specified. + * This is the default. + * + * \sa gridSubStepWidth + */ void setGridSubStepWidth( qreal subStepWidth = 0.0 ); + + /** + * Returns the sub-step width to be used for calculating + * the sub-grid lines. + * + * \sa setGridStepWidth + */ qreal gridSubStepWidth() const; /** - * Specify which granularity sequence is to be used to find a matching - * grid granularity. - * - * See details explained at KChartEnums::GranularitySequence. - * - * You might also want to use setAdjustBoundsToGrid for fine-tuning the - * start/end value. - * - * \sa setAdjustBoundsToGrid, GranularitySequence - */ + * Specifies the granularity sequence to be used for calculating + * the grid lines. + * + * By default the GridAttributes class uses KChartEnums::GranularitySequence_10_20. + * + * \note Granularity can be set for Linear axis calculation mode only, + * there is no way to specify a step width for Logarithmic axes. + * + * \note The sequence specified by this method is ignored, if + * a fixed step width was specified via setStepWidth. + * + * \param sequence one of the sequences declared in + * KChartEnums::GranularitySequence. + * + * \sa gridGranularitySequence, setStepWidth + */ void setGridGranularitySequence( KChartEnums::GranularitySequence sequence ); + + /** + * Returns the granularity sequence to be used for calculating + * the grid lines. + * + * \sa setGridGranularitySequence + */ KChartEnums::GranularitySequence gridGranularitySequence() const; /** * By default visible bounds of the data area are adjusted to match * a main grid line. * If you set the respective adjust flag to false the bound will * not start at a grid line's value but it will be the exact value * of the data range set. * * \sa CartesianCoordinatePlane::setHorizontalRange * \sa CartesianCoordinatePlane::setVerticalRange */ void setAdjustBoundsToGrid( bool adjustLower, bool adjustUpper ); bool adjustLowerBoundToGrid() const; bool adjustUpperBoundToGrid() const; void setGridPen( const QPen & pen ); QPen gridPen() const; void setSubGridVisible( bool visible ); bool isSubGridVisible() const; void setSubGridPen( const QPen & pen ); QPen subGridPen() const; void setOuterLinesVisible( bool visible ); bool isOuterLinesVisible() const; void setZeroLinePen( const QPen & pen ); QPen zeroLinePen() const; bool operator==( const GridAttributes& ) const; inline bool operator!=( const GridAttributes& other ) const { return !operator==(other); } private: KCHART_DECLARE_PRIVATE_BASE_VALUE( GridAttributes ) }; // End of class GridAttributes } #if !defined(QT_NO_DEBUG_STREAM) KCHART_EXPORT QDebug operator<<(QDebug, const KChart::GridAttributes& ); #endif /* QT_NO_DEBUG_STREAM */ KCHART_DECLARE_SWAP_SPECIALISATION( KChart::GridAttributes ) QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO( KChart::GridAttributes, Q_MOVABLE_TYPE ); QT_END_NAMESPACE Q_DECLARE_METATYPE( KChart::GridAttributes ) #endif // KCHARTGRIDATTRIBUTES_H diff --git a/src/KChart/KChartHeaderFooter.cpp b/src/KChart/KChartHeaderFooter.cpp index 655d0b0..69dfdd8 100644 --- a/src/KChart/KChartHeaderFooter.cpp +++ b/src/KChart/KChartHeaderFooter.cpp @@ -1,134 +1,131 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartHeaderFooter.h" #include "KChartHeaderFooter_p.h" #include "KChartChart.h" #include #include "KTextDocument.h" #include "KChartMath_p.h" #include #include #include #include #include #include #include using namespace KChart; HeaderFooter::Private::Private() : type( Header ), position( Position::North ) { } HeaderFooter::Private::~Private() { } #define d d_func() HeaderFooter::HeaderFooter( Chart* parent ) : TextArea( new Private() ) { setParent( parent ); init(); } HeaderFooter::~HeaderFooter() { emit destroyedHeaderFooter( this ); } void HeaderFooter::setParent( QObject* parent ) { QObject::setParent( parent ); setParentWidget( qobject_cast( parent ) ); if ( parent && ! autoReferenceArea() ) setAutoReferenceArea( parent ); } void HeaderFooter::init() { TextAttributes ta; ta.setPen( QPen(Qt::black) ); ta.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Bold, false ) ); Measure m( 35.0 ); m.setRelativeMode( autoReferenceArea(), KChartEnums::MeasureOrientationMinimum ); ta.setFontSize( m ); m.setValue( 8.0 ); m.setCalculationMode( KChartEnums::MeasureCalculationModeAbsolute ); ta.setMinimalFontSize( m ); setTextAttributes( ta ); } -/** - * Creates an exact copy of this header/footer. - */ HeaderFooter * HeaderFooter::clone() const { HeaderFooter* headerFooter = new HeaderFooter( new Private( *d ), nullptr ); headerFooter->setType( type() ); headerFooter->setPosition( position() ); headerFooter->setText( text() ); headerFooter->setTextAttributes( textAttributes() ); return headerFooter; } bool HeaderFooter::compare( const HeaderFooter& other ) const { return (type() == other.type()) && (position() == other.position()) && // also compare members inherited from the base class: (autoReferenceArea() == other.autoReferenceArea()) && (text() == other.text()) && (textAttributes() == other.textAttributes()); } void HeaderFooter::setType( HeaderFooterType type ) { if ( d->type != type ) { d->type = type; emit positionChanged( this ); } } HeaderFooter::HeaderFooterType HeaderFooter::type() const { return d->type; } void HeaderFooter::setPosition( Position position ) { if ( d->position != position ) { d->position = position; emit positionChanged( this ); } } Position HeaderFooter::position() const { return d->position; } diff --git a/src/KChart/KChartHeaderFooter.h b/src/KChart/KChartHeaderFooter.h index 6b73a63..79fb210 100644 --- a/src/KChart/KChartHeaderFooter.h +++ b/src/KChart/KChartHeaderFooter.h @@ -1,68 +1,71 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTHEADERFOOTER_H #define KCHARTHEADERFOOTER_H #include "KChartTextArea.h" #include "KChartPosition.h" namespace KChart { class Chart; class TextAttributes; /** * @brief A header or footer displaying text above or below charts */ class KCHART_EXPORT HeaderFooter : public TextArea { Q_OBJECT KCHART_DECLARE_PRIVATE_DERIVED_PARENT( HeaderFooter, Chart* ) public: HeaderFooter( Chart* parent = nullptr ); virtual ~HeaderFooter(); + /** + * Creates an exact copy of this header/footer. + */ virtual HeaderFooter * clone() const; bool compare( const HeaderFooter& other ) const; enum HeaderFooterType{ Header, Footer }; void setType( HeaderFooterType type ); HeaderFooterType type() const; void setPosition( Position position ); Position position() const; void setParent( QObject* parent ); Q_SIGNALS: void destroyedHeaderFooter( HeaderFooter* ); void positionChanged( HeaderFooter* ); }; // End of class HeaderFooter } #endif // KCHARTHEADERFOOTER_H diff --git a/src/KChart/KChartLayoutItems.cpp b/src/KChart/KChartLayoutItems.cpp index 2e86560..1acfaa0 100644 --- a/src/KChart/KChartLayoutItems.cpp +++ b/src/KChart/KChartLayoutItems.cpp @@ -1,1026 +1,1002 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLayoutItems.h" #include "KTextDocument.h" #include "KChartAbstractArea.h" #include "KChartAbstractDiagram.h" #include "KChartBackgroundAttributes.h" #include "KChartFrameAttributes.h" #include "KChartPaintContext.h" #include "KChartPainterSaver_p.h" #include "KChartPrintingParameters.h" #include "KChartMath_p.h" #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_ITEMS_PAINT -/** - Inform the item about its widget: This enables the item, - to trigger that widget's update, whenever the size of the item's - contents has changed. - - Thus, you need to call setParentWidget on every item, that - has a non-fixed size. - */ void KChart::AbstractLayoutItem::setParentWidget( QWidget* widget ) { mParent = widget; } void KChart::AbstractLayoutItem::paintAll( QPainter& painter ) { paint( &painter ); } -/** - * Default impl: Paint the complete item using its layouted position and size. - */ void KChart::AbstractLayoutItem::paintCtx( PaintContext* context ) { if ( context ) paint( context->painter() ); } -/** - Report changed size hint: ask the parent widget to recalculate the layout. - */ void KChart::AbstractLayoutItem::sizeHintChanged() const { // This is exactly like what QWidget::updateGeometry does. // qDebug("KChart::AbstractLayoutItem::sizeHintChanged() called"); if ( mParent ) { if ( mParent->layout() ) mParent->layout()->invalidate(); else QApplication::postEvent( mParent, new QEvent( QEvent::LayoutRequest ) ); } } KChart::TextBubbleLayoutItem::TextBubbleLayoutItem( const QString& text, const KChart::TextAttributes& attributes, const QObject* area, KChartEnums::MeasureOrientation orientation, Qt::Alignment alignment ) : AbstractLayoutItem( alignment ), m_text( new TextLayoutItem( text, attributes, area, orientation, alignment ) ) { } KChart::TextBubbleLayoutItem::TextBubbleLayoutItem() : AbstractLayoutItem( Qt::AlignLeft ), m_text( new TextLayoutItem() ) { } KChart::TextBubbleLayoutItem::~TextBubbleLayoutItem() { delete m_text; } void KChart::TextBubbleLayoutItem::setAutoReferenceArea( const QObject* area ) { m_text->setAutoReferenceArea( area ); } const QObject* KChart::TextBubbleLayoutItem::autoReferenceArea() const { return m_text->autoReferenceArea(); } void KChart::TextBubbleLayoutItem::setText( const QString& text ) { m_text->setText( text ); } QString KChart::TextBubbleLayoutItem::text() const { return m_text->text(); } void KChart::TextBubbleLayoutItem::setTextAttributes( const TextAttributes& a ) { m_text->setTextAttributes( a ); } KChart::TextAttributes KChart::TextBubbleLayoutItem::textAttributes() const { return m_text->textAttributes(); } bool KChart::TextBubbleLayoutItem::isEmpty() const { return m_text->isEmpty(); } Qt::Orientations KChart::TextBubbleLayoutItem::expandingDirections() const { return m_text->expandingDirections(); } QSize KChart::TextBubbleLayoutItem::maximumSize() const { const int border = borderWidth(); return m_text->maximumSize() + QSize( 2 * border, 2 * border ); } QSize KChart::TextBubbleLayoutItem::minimumSize() const { const int border = borderWidth(); return m_text->minimumSize() + QSize( 2 * border, 2 * border ); } QSize KChart::TextBubbleLayoutItem::sizeHint() const { const int border = borderWidth(); return m_text->sizeHint() + QSize( 2 * border, 2 * border ); } void KChart::TextBubbleLayoutItem::setGeometry( const QRect& r ) { const int border = borderWidth(); m_text->setGeometry( r.adjusted( border, border, -border, -border ) ); } QRect KChart::TextBubbleLayoutItem::geometry() const { const int border = borderWidth(); return m_text->geometry().adjusted( -border, -border, border, border ); } void KChart::TextBubbleLayoutItem::paint( QPainter* painter ) { const QPen oldPen = painter->pen(); const QBrush oldBrush = painter->brush(); painter->setPen( Qt::black ); painter->setBrush( QColor( 255, 255, 220 ) ); painter->drawRoundRect( geometry(), 10 ); painter->setPen( oldPen ); painter->setBrush( oldBrush ); m_text->paint( painter ); } int KChart::TextBubbleLayoutItem::borderWidth() const { return 1; } KChart::TextLayoutItem::TextLayoutItem( const QString& text, const KChart::TextAttributes& attributes, const QObject* area, KChartEnums::MeasureOrientation orientation, Qt::Alignment alignment ) : AbstractLayoutItem( alignment ) , mText( text ) , mTextAlignment( alignment ) , mAttributes( attributes ) , mAutoReferenceArea( area ) , mAutoReferenceOrientation( orientation ) , cachedSizeHint() // default this to invalid to force just-in-time calculation before first use of sizeHint() , cachedFontSize( 0.0 ) , cachedFont( mAttributes.font() ) { } KChart::TextLayoutItem::TextLayoutItem() : AbstractLayoutItem( Qt::AlignLeft ) , mText() , mTextAlignment( Qt::AlignLeft ) , mAttributes() , mAutoReferenceArea( nullptr ) , mAutoReferenceOrientation( KChartEnums::MeasureOrientationHorizontal ) , cachedSizeHint() // default this to invalid to force just-in-time calculation before first use of sizeHint() , cachedFontSize( 0.0 ) , cachedFont( mAttributes.font() ) { } void KChart::TextLayoutItem::setAutoReferenceArea( const QObject* area ) { mAutoReferenceArea = area; cachedSizeHint = QSize(); sizeHint(); } const QObject* KChart::TextLayoutItem::autoReferenceArea() const { return mAutoReferenceArea; } void KChart::TextLayoutItem::setText(const QString & text) { mText = text; cachedSizeHint = QSize(); sizeHint(); if ( mParent ) mParent->update(); } QString KChart::TextLayoutItem::text() const { return mText; } void KChart::TextLayoutItem::setTextAlignment( Qt::Alignment alignment) { if ( mTextAlignment == alignment ) return; mTextAlignment = alignment; if ( mParent ) mParent->update(); } Qt::Alignment KChart::TextLayoutItem::textAlignment() const { return mTextAlignment; } -/** - \brief Use this to specify the text attributes to be used for this item. - - \sa textAttributes -*/ void KChart::TextLayoutItem::setTextAttributes( const TextAttributes &a ) { mAttributes = a; cachedFont = a.font(); cachedSizeHint = QSize(); // invalidate size hint sizeHint(); if ( mParent ) mParent->update(); } -/** - Returns the text attributes to be used for this item. - - \sa setTextAttributes -*/ KChart::TextAttributes KChart::TextLayoutItem::textAttributes() const { return mAttributes; } Qt::Orientations KChart::TextLayoutItem::expandingDirections() const { return Qt::Orientations(); // Grow neither vertically nor horizontally } QRect KChart::TextLayoutItem::geometry() const { return mRect; } bool KChart::TextLayoutItem::isEmpty() const { return false; // never empty, otherwise the layout item would not exist } QSize KChart::TextLayoutItem::maximumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } QSize KChart::TextLayoutItem::minimumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } void KChart::TextLayoutItem::setGeometry( const QRect& r ) { mRect = r; } // returns the bounding box of rect rotated around its center QRectF rotatedRect( const QRectF& rect, qreal rotation ) { QTransform t; QPointF center = rect.center(); t.translate( center.x(), center.y() ); t.rotate( rotation ); t.translate( -center.x(), -center.y() ); return t.mapRect( rect ); } qreal KChart::TextLayoutItem::fitFontSizeToGeometry() const { QFont f = realFont(); const qreal origResult = f.pointSizeF(); qreal result = origResult; const qreal minSize = mAttributes.minimalFontSize().value(); const QSize mySize = geometry().size(); if ( mySize.isNull() ) { return result; } QFontMetrics fm( f ); while ( true ) { const QSizeF textSize = rotatedRect( fm.boundingRect( mText ), mAttributes.rotation() ).normalized().size(); if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() ) { return result; } result -= 0.5; if ( minSize > 0 && result < minSize ) { return result + 0.5; } else if ( result <= 0.0 ) { return origResult; } f.setPointSizeF( result ); fm = QFontMetrics( f ); } } qreal KChart::TextLayoutItem::realFontSize() const { return mAttributes.calculatedFontSize( mAutoReferenceArea, mAutoReferenceOrientation ); } bool KChart::TextLayoutItem::maybeUpdateRealFont() const { const qreal fntSiz = realFontSize(); const bool doUpdate = !cachedSizeHint.isValid() || cachedFontSize != fntSiz; if ( doUpdate && fntSiz > 0.0 ) { cachedFontSize = fntSiz; cachedFont.setPointSizeF( fntSiz ); } return doUpdate; // "didUpdate" by now } QFont KChart::TextLayoutItem::realFont() const { maybeUpdateRealFont(); return cachedFont; } QPolygon KChart::TextLayoutItem::boundingPolygon() const { // should probably call sizeHint() here, but that one is expensive (see TODO there) return mCachedBoundingPolygon; } bool KChart::TextLayoutItem::intersects( const TextLayoutItem& other, const QPointF& myPos, const QPointF& otherPos ) const { return intersects( other, myPos.toPoint(), otherPos.toPoint() ); } bool KChart::TextLayoutItem::intersects( const TextLayoutItem& other, const QPoint& myPos, const QPoint& otherPos ) const { QRegion myRegion( boundingPolygon().translated( myPos - otherPos ) ); QRegion otherRegion( other.boundingPolygon() ); return myRegion.intersects( otherRegion ); } QSize KChart::TextLayoutItem::sizeHint() const { // ### we only really need to recalculate the size hint when mAttributes.rotation has *changed* if ( maybeUpdateRealFont() || mAttributes.rotation() || !cachedSizeHint.isValid() ) { const QSize newSizeHint( calcSizeHint( cachedFont ) ); Q_ASSERT( newSizeHint.isValid() ); if ( newSizeHint != cachedSizeHint ) { cachedSizeHint = newSizeHint; sizeHintChanged(); } } return cachedSizeHint; } QSize KChart::TextLayoutItem::sizeHintUnrotated() const { maybeUpdateRealFont(); // make sure the cached font is up to date return unrotatedSizeHint( cachedFont ); } // PENDING(kalle) Support auto shrink QSize KChart::TextLayoutItem::unrotatedTextSize( QFont fnt ) const { if ( fnt == QFont() ) { fnt = realFont(); // this is the cached font in most cases } const QFontMetricsF fm( fnt, GlobalMeasureScaling::paintDevice() ); QRect veryLarge( 0, 0, 100000, 100000 ); // this overload of boundingRect() interprets \n as line breaks, not as regular characters. return fm.boundingRect( veryLarge, Qt::AlignLeft | Qt::AlignTop, mText ).size().toSize(); } int KChart::TextLayoutItem::marginWidth() const { return marginWidth( unrotatedTextSize() ); } int KChart::TextLayoutItem::marginWidth( const QSize& textSize ) const { return qMin ( QApplication::style()->pixelMetric( QStyle::PM_ButtonMargin, nullptr, nullptr ), // decrease frame size if the text is small textSize.height() * 2 / 3 ); } QSize KChart::TextLayoutItem::unrotatedSizeHint( const QFont& fnt ) const { QSize ret = unrotatedTextSize( fnt ); const int margin = marginWidth( ret ); ret += QSize( margin, margin ); return ret; } QSize KChart::TextLayoutItem::calcSizeHint( const QFont& font ) const { const QSize size = unrotatedSizeHint( font ); QPoint topLeft( -size.width() * 0.5, -size.height() * 0.5 ); if ( !mAttributes.rotation() ) { mCachedBoundingPolygon.resize( 4 ); // using the same winding order as returned by QPolygon QTransform::mapToPolygon(const QRect&), // which is: 0-1: top edge, 1-2: right edge, 2-3: bottom edge, 3-0: left edge (of input rect) mCachedBoundingPolygon[ 0 ] = topLeft; mCachedBoundingPolygon[ 1 ] = topLeft + QPoint( size.width(), 0 ); // top right mCachedBoundingPolygon[ 2 ] = topLeft + QPoint( size.width(), size.height() ); // bottom right mCachedBoundingPolygon[ 3 ] = topLeft + QPoint( 0, size.height() ); // bottom left return size; } const QRect rect( topLeft, size ); QTransform t; t.rotate( mAttributes.rotation() ); mCachedBoundingPolygon = t.mapToPolygon( rect ); return mCachedBoundingPolygon.boundingRect().size(); } void KChart::TextLayoutItem::paint( QPainter* painter ) { if ( !mRect.isValid() ) { return; } const PainterSaver painterSaver( painter ); QFont f = realFont(); if ( mAttributes.autoShrink() ) { f.setPointSizeF( fitFontSizeToGeometry() ); } painter->setFont( f ); QSize innerSize = unrotatedTextSize(); QRectF rect = QRectF( QPointF( 0, 0 ), innerSize ); rect.translate( -rect.center() ); painter->translate( mRect.center() ); painter->rotate( mAttributes.rotation() ); #ifdef DEBUG_ITEMS_PAINT painter->setPen( Qt::red ); painter->drawRect( rect ); #endif painter->setPen( PrintingParameters::scalePen( mAttributes.pen() ) ); QTextDocument* document = mAttributes.textDocument(); if ( document ) { document->setPageSize( rect.size() ); document->setHtml( mText ); QAbstractTextDocumentLayout::PaintContext paintcontext; // ### this doesn't work for rotated painting because clip does not translate the painting // TODO translate the painting either using a QTransform or one of QPainter's transform stages paintcontext.clip = rect; document->documentLayout()->draw( painter, paintcontext ); } else { painter->drawText( rect, mTextAlignment, mText ); } } KChart::HorizontalLineLayoutItem::HorizontalLineLayoutItem() : AbstractLayoutItem( Qt::AlignCenter ) { } Qt::Orientations KChart::HorizontalLineLayoutItem::expandingDirections() const { return Qt::Horizontal; } QRect KChart::HorizontalLineLayoutItem::geometry() const { return mRect; } bool KChart::HorizontalLineLayoutItem::isEmpty() const { return false; // never empty, otherwise the layout item would not exist } QSize KChart::HorizontalLineLayoutItem::maximumSize() const { return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); } QSize KChart::HorizontalLineLayoutItem::minimumSize() const { return QSize( 0, 0 ); } void KChart::HorizontalLineLayoutItem::setGeometry( const QRect& r ) { mRect = r; } QSize KChart::HorizontalLineLayoutItem::sizeHint() const { return QSize( -1, 3 ); // see qframe.cpp } void KChart::HorizontalLineLayoutItem::paint( QPainter* painter ) { if ( !mRect.isValid() ) return; painter->drawLine( QPointF( mRect.left(), mRect.center().y() ), QPointF( mRect.right(), mRect.center().y() ) ); } KChart::VerticalLineLayoutItem::VerticalLineLayoutItem() : AbstractLayoutItem( Qt::AlignCenter ) { } Qt::Orientations KChart::VerticalLineLayoutItem::expandingDirections() const { return Qt::Vertical; } QRect KChart::VerticalLineLayoutItem::geometry() const { return mRect; } bool KChart::VerticalLineLayoutItem::isEmpty() const { return false; // never empty, otherwise the layout item would not exist } QSize KChart::VerticalLineLayoutItem::maximumSize() const { return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); } QSize KChart::VerticalLineLayoutItem::minimumSize() const { return QSize( 0, 0 ); } void KChart::VerticalLineLayoutItem::setGeometry( const QRect& r ) { mRect = r; } QSize KChart::VerticalLineLayoutItem::sizeHint() const { return QSize( 3, -1 ); // see qframe.cpp } void KChart::VerticalLineLayoutItem::paint( QPainter* painter ) { if ( !mRect.isValid() ) return; painter->drawLine( QPointF( mRect.center().x(), mRect.top() ), QPointF( mRect.center().x(), mRect.bottom() ) ); } KChart::MarkerLayoutItem::MarkerLayoutItem( KChart::AbstractDiagram* diagram, const MarkerAttributes& marker, const QBrush& brush, const QPen& pen, Qt::Alignment alignment ) : AbstractLayoutItem( alignment ) , mDiagram( diagram ) , mMarker( marker ) , mBrush( brush ) , mPen( pen ) { } Qt::Orientations KChart::MarkerLayoutItem::expandingDirections() const { return Qt::Orientations(); // Grow neither vertically nor horizontally } QRect KChart::MarkerLayoutItem::geometry() const { return mRect; } bool KChart::MarkerLayoutItem::isEmpty() const { return false; // never empty, otherwise the layout item would not exist } QSize KChart::MarkerLayoutItem::maximumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } QSize KChart::MarkerLayoutItem::minimumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } void KChart::MarkerLayoutItem::setGeometry( const QRect& r ) { mRect = r; } QSize KChart::MarkerLayoutItem::sizeHint() const { //qDebug() << "KChart::MarkerLayoutItem::sizeHint() returns:"<(( rect.width() - siz.width()) / 2.0 ), static_cast(( rect.height() - siz.height()) / 2.0 ) ); #ifdef DEBUG_ITEMS_PAINT QPointF oldPos = pos; #endif // And finally, drawMarker() assumes the position to be the center // of the marker, adjust again. pos += QPointF( static_cast( siz.width() ) / 2.0, static_cast( siz.height() )/ 2.0 ); diagram->paintMarker( painter, marker, brush, pen, pos.toPoint(), siz ); #ifdef DEBUG_ITEMS_PAINT const QPen oldPen( painter->pen() ); painter->setPen( Qt::red ); painter->drawRect( QRect( oldPos.toPoint(), siz ) ); painter->setPen( oldPen ); #endif } KChart::LineLayoutItem::LineLayoutItem( KChart::AbstractDiagram* diagram, int length, const QPen& pen, Qt::Alignment legendLineSymbolAlignment, Qt::Alignment alignment ) : AbstractLayoutItem( alignment ) , mDiagram( diagram ) , mLength( length ) , mPen( pen ) , mLegendLineSymbolAlignment(legendLineSymbolAlignment) { // enforce a minimum pen width if ( pen.width() < 2 ) mPen.setWidth( 2 ); } Qt::Orientations KChart::LineLayoutItem::expandingDirections() const { return Qt::Orientations(); // Grow neither vertically nor horizontally } QRect KChart::LineLayoutItem::geometry() const { return mRect; } bool KChart::LineLayoutItem::isEmpty() const { return false; // never empty, otherwise the layout item would not exist } QSize KChart::LineLayoutItem::maximumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } QSize KChart::LineLayoutItem::minimumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } void KChart::LineLayoutItem::setGeometry( const QRect& r ) { mRect = r; } QSize KChart::LineLayoutItem::sizeHint() const { return QSize( mLength, mPen.width() + 2 ); } void KChart::LineLayoutItem::setLegendLineSymbolAlignment(Qt::Alignment legendLineSymbolAlignment) { if (mLegendLineSymbolAlignment == legendLineSymbolAlignment) return; mLegendLineSymbolAlignment = legendLineSymbolAlignment; } Qt::Alignment KChart::LineLayoutItem::legendLineSymbolAlignment() const { return mLegendLineSymbolAlignment; } void KChart::LineLayoutItem::paint( QPainter* painter ) { paintIntoRect( painter, mRect, mPen, mLegendLineSymbolAlignment ); } void KChart::LineLayoutItem::paintIntoRect( QPainter* painter, const QRect& rect, const QPen& pen, Qt::Alignment lineAlignment) { if ( ! rect.isValid() ) return; const QPen oldPen = painter->pen(); painter->setPen( PrintingParameters::scalePen( pen ) ); qreal y = 0; if (lineAlignment == Qt::AlignTop) y = rect.top(); else if (lineAlignment == Qt::AlignBottom) y = rect.bottom(); else y = rect.center().y(); painter->drawLine( QPointF( rect.left(), y ), QPointF( rect.right(), y ) ); painter->setPen( oldPen ); } KChart::LineWithMarkerLayoutItem::LineWithMarkerLayoutItem( KChart::AbstractDiagram* diagram, int lineLength, const QPen& linePen, int markerOffs, const MarkerAttributes& marker, const QBrush& markerBrush, const QPen& markerPen, Qt::Alignment alignment ) : AbstractLayoutItem( alignment ) , mDiagram( diagram ) , mLineLength( lineLength ) , mLinePen( linePen ) , mMarkerOffs( markerOffs ) , mMarker( marker ) , mMarkerBrush( markerBrush ) , mMarkerPen( markerPen ) { } Qt::Orientations KChart::LineWithMarkerLayoutItem::expandingDirections() const { return Qt::Orientations(); // Grow neither vertically nor horizontally } QRect KChart::LineWithMarkerLayoutItem::geometry() const { return mRect; } bool KChart::LineWithMarkerLayoutItem::isEmpty() const { return false; // never empty, otherwise the layout item would not exist } QSize KChart::LineWithMarkerLayoutItem::maximumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } QSize KChart::LineWithMarkerLayoutItem::minimumSize() const { return sizeHint(); // PENDING(kalle) Review, quite inflexible } void KChart::LineWithMarkerLayoutItem::setGeometry( const QRect& r ) { mRect = r; } QSize KChart::LineWithMarkerLayoutItem::sizeHint() const { const QSize lineSize( mLineLength, mLinePen.width() + 2 ); return lineSize.expandedTo( mMarker.markerSize().toSize() ); } void KChart::LineWithMarkerLayoutItem::paint( QPainter* painter ) { // paint the line over the full width, into the vertical middle of the rect LineLayoutItem::paintIntoRect( painter, mRect, mLinePen, Qt::AlignCenter ); // paint the marker with the given offset from the left side of the line const QRect r( QPoint( mRect.x()+mMarkerOffs, mRect.y() ), QSize( mMarker.markerSize().toSize().width(), mRect.height() ) ); MarkerLayoutItem::paintIntoRect( painter, r, mDiagram, mMarker, mMarkerBrush, mMarkerPen ); } KChart::AutoSpacerLayoutItem::AutoSpacerLayoutItem( bool layoutIsAtTopPosition, QHBoxLayout *rightLeftLayout, bool layoutIsAtLeftPosition, QVBoxLayout *topBottomLayout ) : AbstractLayoutItem( Qt::AlignCenter ) , mLayoutIsAtTopPosition( layoutIsAtTopPosition ) , mRightLeftLayout( rightLeftLayout ) , mLayoutIsAtLeftPosition( layoutIsAtLeftPosition ) , mTopBottomLayout( topBottomLayout ) { } Qt::Orientations KChart::AutoSpacerLayoutItem::expandingDirections() const { return Qt::Orientations(); // Grow neither vertically nor horizontally } QRect KChart::AutoSpacerLayoutItem::geometry() const { return mRect; } bool KChart::AutoSpacerLayoutItem::isEmpty() const { return true; // never empty, otherwise the layout item would not exist } QSize KChart::AutoSpacerLayoutItem::maximumSize() const { return sizeHint(); } QSize KChart::AutoSpacerLayoutItem::minimumSize() const { return sizeHint(); } void KChart::AutoSpacerLayoutItem::setGeometry( const QRect& r ) { mRect = r; } static void updateCommonBrush( QBrush& commonBrush, bool& bStart, const KChart::AbstractArea& area ) { const KChart::BackgroundAttributes ba( area.backgroundAttributes() ); const bool hasSimpleBrush = ( ! area.frameAttributes().isVisible() && ba.isVisible() && ba.pixmapMode() == KChart::BackgroundAttributes::BackgroundPixmapModeNone && ba.brush().gradient() == nullptr ); if ( bStart ) { bStart = false; commonBrush = hasSimpleBrush ? ba.brush() : QBrush(); } else { if ( ! hasSimpleBrush || ba.brush() != commonBrush ) { commonBrush = QBrush(); } } } QSize KChart::AutoSpacerLayoutItem::sizeHint() const { QBrush commonBrush; bool bStart=true; // calculate the maximal overlap of the top/bottom axes: int topBottomOverlap = 0; if ( mTopBottomLayout ) { for (int i = 0; i < mTopBottomLayout->count(); ++i) { AbstractArea* area = dynamic_cast(mTopBottomLayout->itemAt(i)); if ( area ) { //qDebug() << "AutoSpacerLayoutItem testing" << area; topBottomOverlap = qMax( topBottomOverlap, mLayoutIsAtLeftPosition ? area->rightOverlap() : area->leftOverlap() ); updateCommonBrush( commonBrush, bStart, *area ); } } } // calculate the maximal overlap of the left/right axes: int leftRightOverlap = 0; if ( mRightLeftLayout ) { for (int i = 0; i < mRightLeftLayout->count(); ++i) { AbstractArea* area = dynamic_cast(mRightLeftLayout->itemAt(i)); if ( area ) { //qDebug() << "AutoSpacerLayoutItem testing" << area; leftRightOverlap = qMax( leftRightOverlap, mLayoutIsAtTopPosition ? area->bottomOverlap() : area->topOverlap() ); updateCommonBrush( commonBrush, bStart, *area ); } } } if ( topBottomOverlap > 0 && leftRightOverlap > 0 ) mCommonBrush = commonBrush; else mCommonBrush = QBrush(); mCachedSize = QSize( topBottomOverlap, leftRightOverlap ); //qDebug() << mCachedSize; return mCachedSize; } void KChart::AutoSpacerLayoutItem::paint( QPainter* painter ) { if ( mParentLayout && mRect.isValid() && mCachedSize.isValid() && mCommonBrush.style() != Qt::NoBrush ) { QPoint p1( mRect.topLeft() ); QPoint p2( mRect.bottomRight() ); if ( mLayoutIsAtLeftPosition ) p1.rx() += mCachedSize.width() - mParentLayout->spacing(); else p2.rx() -= mCachedSize.width() - mParentLayout->spacing(); if ( mLayoutIsAtTopPosition ) { p1.ry() += mCachedSize.height() - mParentLayout->spacing() - 1; p2.ry() -= 1; } else p2.ry() -= mCachedSize.height() - mParentLayout->spacing() - 1; //qDebug() << mLayoutIsAtTopPosition << mLayoutIsAtLeftPosition; //qDebug() << mRect; //qDebug() << mParentLayout->margin(); //qDebug() << QRect( p1, p2 ); const QPoint oldBrushOrigin( painter->brushOrigin() ); const QBrush oldBrush( painter->brush() ); const QPen oldPen( painter->pen() ); const QPointF newTopLeft( painter->deviceMatrix().map( p1 ) ); painter->setBrushOrigin( newTopLeft ); painter->setBrush( mCommonBrush ); painter->setPen( Qt::NoPen ); painter->drawRect( QRect( p1, p2 ) ); painter->setBrushOrigin( oldBrushOrigin ); painter->setBrush( oldBrush ); painter->setPen( oldPen ); } // debug code: #if 0 //qDebug() << "KChart::AutoSpacerLayoutItem::paint()"; if ( !mRect.isValid() ) return; painter->drawRect( mRect ); painter->drawLine( QPointF( mRect.topLeft(), mRect.bottomRight() ) ); painter->drawLine( QPointF( mRect.topRight(), mRect.bottomLeft() ) ); #endif } diff --git a/src/KChart/KChartLayoutItems.h b/src/KChart/KChartLayoutItems.h index 47e9d70..b962e8a 100644 --- a/src/KChart/KChartLayoutItems.h +++ b/src/KChart/KChartLayoutItems.h @@ -1,476 +1,504 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTLAYOUTITEMS_H #define KCHARTLAYOUTITEMS_H #include #include #include #include #include #include #include "KChartTextAttributes.h" #include "KChartMarkerAttributes.h" QT_BEGIN_NAMESPACE class QPainter; class KTextDocument; QT_END_NAMESPACE // TODO remove QRectF rotatedRect( const QRectF& pt, qreal rotation ); namespace KChart { class AbstractDiagram; class PaintContext; /** * Base class for all layout items of KChart * \internal */ class KCHART_EXPORT AbstractLayoutItem : public QLayoutItem { public: AbstractLayoutItem( Qt::Alignment itemAlignment = Qt::Alignment() ) : QLayoutItem( itemAlignment ), mParent( nullptr ), mParentLayout( nullptr ) {} /** * Default impl: just call paint. * * Derived classes like KChart::AbstractArea are providing * additional action here. */ virtual void paintAll( QPainter& painter ); virtual void paint( QPainter* ) = 0; + + /** + * Default impl: Paint the complete item using its layouted position and size. + */ virtual void paintCtx( PaintContext* context ); + + /** + Inform the item about its widget: This enables the item, + to trigger that widget's update, whenever the size of the item's + contents has changed. + + Thus, you need to call setParentWidget on every item, that + has a non-fixed size. + */ virtual void setParentWidget( QWidget* widget ); + + /** + Report changed size hint: ask the parent widget to recalculate the layout. + */ virtual void sizeHintChanged() const; void setParentLayout( QLayout* lay ) { mParentLayout = lay; } QLayout* parentLayout() { return mParentLayout; } void removeFromParentLayout() { if ( mParentLayout ) { if ( widget() ) mParentLayout->removeWidget( widget() ); else mParentLayout->removeItem( this ); } } protected: QWidget* mParent; QLayout* mParentLayout; }; /** * Layout item showing a text *\internal */ class KCHART_EXPORT TextLayoutItem : public AbstractLayoutItem { public: TextLayoutItem(); TextLayoutItem( const QString& text, const TextAttributes& attributes, const QObject* autoReferenceArea, KChartEnums::MeasureOrientation autoReferenceOrientation, Qt::Alignment alignment = Qt::Alignment() ); void setAutoReferenceArea( const QObject* area ); const QObject* autoReferenceArea() const; void setText(const QString & text); QString text() const; void setTextAlignment( Qt::Alignment ); Qt::Alignment textAlignment() const; + /** + \brief Use this to specify the text attributes to be used for this item. + + \sa textAttributes + */ void setTextAttributes( const TextAttributes& a ); + + /** + Returns the text attributes to be used for this item. + + \sa setTextAttributes + */ TextAttributes textAttributes() const; /** pure virtual in QLayoutItem */ bool isEmpty() const override; /** pure virtual in QLayoutItem */ Qt::Orientations expandingDirections() const override; /** pure virtual in QLayoutItem */ QSize maximumSize() const override; /** pure virtual in QLayoutItem */ QSize minimumSize() const override; /** pure virtual in QLayoutItem */ QSize sizeHint() const override; /** pure virtual in QLayoutItem */ void setGeometry( const QRect& r ) override; /** pure virtual in QLayoutItem */ QRect geometry() const override; virtual int marginWidth() const; virtual QSize sizeHintUnrotated() const; virtual bool intersects( const TextLayoutItem& other, const QPointF& myPos, const QPointF& otherPos ) const; virtual bool intersects( const TextLayoutItem& other, const QPoint& myPos, const QPoint& otherPos ) const; virtual qreal realFontSize() const; virtual QFont realFont() const; void paint( QPainter* ) override; QPolygon boundingPolygon() const; private: bool maybeUpdateRealFont() const; QSize unrotatedSizeHint( const QFont& fnt = QFont() ) const; QSize unrotatedTextSize( QFont fnt = QFont() ) const; QSize calcSizeHint( const QFont& font ) const; int marginWidth( const QSize& textSize ) const; qreal fitFontSizeToGeometry() const; QRect mRect; QString mText; Qt::Alignment mTextAlignment; TextAttributes mAttributes; const QObject* mAutoReferenceArea; KChartEnums::MeasureOrientation mAutoReferenceOrientation; mutable QSize cachedSizeHint; mutable QPolygon mCachedBoundingPolygon; mutable qreal cachedFontSize; mutable QFont cachedFont; }; class KCHART_EXPORT TextBubbleLayoutItem : public AbstractLayoutItem { public: TextBubbleLayoutItem(); TextBubbleLayoutItem( const QString& text, const TextAttributes& attributes, const QObject* autoReferenceArea, KChartEnums::MeasureOrientation autoReferenceOrientation, Qt::Alignment alignment = Qt::Alignment() ); ~TextBubbleLayoutItem(); void setAutoReferenceArea( const QObject* area ); const QObject* autoReferenceArea() const; void setText(const QString & text); QString text() const; void setTextAttributes( const TextAttributes& a ); TextAttributes textAttributes() const; /** pure virtual in QLayoutItem */ bool isEmpty() const override; /** pure virtual in QLayoutItem */ Qt::Orientations expandingDirections() const override; /** pure virtual in QLayoutItem */ QSize maximumSize() const override; /** pure virtual in QLayoutItem */ QSize minimumSize() const override; /** pure virtual in QLayoutItem */ QSize sizeHint() const override; /** pure virtual in QLayoutItem */ void setGeometry( const QRect& r ) override; /** pure virtual in QLayoutItem */ QRect geometry() const override; void paint( QPainter* painter ) override; protected: int borderWidth() const; private: TextLayoutItem* const m_text; }; /** * Layout item showing a data point marker * \internal */ class KCHART_EXPORT MarkerLayoutItem : public AbstractLayoutItem { public: MarkerLayoutItem( AbstractDiagram* diagram, const MarkerAttributes& marker, const QBrush& brush, const QPen& pen, Qt::Alignment alignment = Qt::Alignment() ); Qt::Orientations expandingDirections() const override; QRect geometry() const override; bool isEmpty() const override; QSize maximumSize() const override; QSize minimumSize() const override; void setGeometry( const QRect& r ) override; QSize sizeHint() const override; void paint( QPainter* ) override; static void paintIntoRect( QPainter* painter, const QRect& rect, AbstractDiagram* diagram, const MarkerAttributes& marker, const QBrush& brush, const QPen& pen ); private: AbstractDiagram* mDiagram; QRect mRect; MarkerAttributes mMarker; QBrush mBrush; QPen mPen; }; /** * Layout item showing a coloured line * \internal */ class KCHART_EXPORT LineLayoutItem : public AbstractLayoutItem { public: LineLayoutItem( AbstractDiagram* diagram, int length, const QPen& pen, Qt::Alignment mLegendLineSymbolAlignment, Qt::Alignment alignment = Qt::Alignment() ); Qt::Orientations expandingDirections() const override; QRect geometry() const override; bool isEmpty() const override; QSize maximumSize() const override; QSize minimumSize() const override; void setGeometry( const QRect& r ) override; QSize sizeHint() const override; void setLegendLineSymbolAlignment(Qt::Alignment legendLineSymbolAlignment); virtual Qt::Alignment legendLineSymbolAlignment() const; void paint( QPainter* ) override; static void paintIntoRect( QPainter* painter, const QRect& rect, const QPen& pen, Qt::Alignment lineAlignment); private: AbstractDiagram* mDiagram; //TODO: not used. remove it int mLength; QPen mPen; QRect mRect; Qt::Alignment mLegendLineSymbolAlignment; }; /** * Layout item showing a coloured line and a data point marker * \internal */ class KCHART_EXPORT LineWithMarkerLayoutItem : public AbstractLayoutItem { public: LineWithMarkerLayoutItem( AbstractDiagram* diagram, int lineLength, const QPen& linePen, int markerOffs, const MarkerAttributes& marker, const QBrush& markerBrush, const QPen& markerPen, Qt::Alignment alignment = Qt::Alignment() ); Qt::Orientations expandingDirections() const override; QRect geometry() const override; bool isEmpty() const override; QSize maximumSize() const override; QSize minimumSize() const override; void setGeometry( const QRect& r ) override; QSize sizeHint() const override; void paint( QPainter* ) override; private: AbstractDiagram* mDiagram; QRect mRect; int mLineLength; QPen mLinePen; int mMarkerOffs; MarkerAttributes mMarker; QBrush mMarkerBrush; QPen mMarkerPen; }; /** * Layout item showing a horizontal line * \internal */ class KCHART_EXPORT HorizontalLineLayoutItem : public AbstractLayoutItem { public: HorizontalLineLayoutItem(); Qt::Orientations expandingDirections() const override; QRect geometry() const override; bool isEmpty() const override; QSize maximumSize() const override; QSize minimumSize() const override; void setGeometry( const QRect& r ) override; QSize sizeHint() const override; void paint( QPainter* ) override; private: QRect mRect; }; /** * Layout item showing a vertial line * \internal */ class KCHART_EXPORT VerticalLineLayoutItem : public AbstractLayoutItem { public: VerticalLineLayoutItem(); Qt::Orientations expandingDirections() const override; QRect geometry() const override; bool isEmpty() const override; QSize maximumSize() const override; QSize minimumSize() const override; void setGeometry( const QRect& r ) override; QSize sizeHint() const override; void paint( QPainter* ) override; private: QRect mRect; }; /** * @brief An empty layout item * \internal * * The AutoSpacerLayoutItem is automatically put into each corner cell of * the planeLayout grid: one of its reference-layouts is a QVBoxLayout (for * the top, or bottom axes resp.), the other one is a QHBoxLayout (for the * left/right sided axes). * * The spacer reserves enough space so all of the AbstractAreas contained * in the two reference-layouts can display not only their in-bounds * content but also their overlapping content reaching out of their area. * * KChart's layouting is applying this schema: \verbatim +------------------+-------------------------+-----------------+ | +--------------+ | +---------------------+ | +-------------+ | | | | | | QVBoxLayout for | | | | | | | AUTO | | | the top axis/axes | | | AUTO | | | | SPACER | | +---------------------+ | | SPACER | | | | ITEM | | | | | | ITEM | | | | | | | | | | | | | +--------------+ | +---------------------+ | +-------------+ | +------------------+-------------------------+-----------------+ | +--------+-----+ | +---------------------+ | +-------+-----+ | | | | | | | | | | | | | | | | | | | | | | | | | | | QHBox- | | | | | | | Right | | | | | Layout | | | | | | | | | | | | | | | | | | | axes | | | | | for | | | | | | | | | | | | | | | | | | | layout| | | | | the | | | | DIAGRAM(s) | | | | | | | | | | | | | | | | | | | | left | | | | | | | | | | | | | | | | | | | | | | | | axis | | | | | | | | | | | | or | | | | | | | | | | | | axes | | | | | | | | | | | | | | | | | | | | | | | +--------+-----+ | +---------------------+ | +-------+-----+ | +------------------+-------------------------+-----------------+ | +--------------+ | +---------------------+ | +-------------+ | | | | | | QVBoxLayout for | | | | | | | AUTO | | | the bottom axes | | | AUTO | | | | SPACER | | +---------------------+ | | SPACER | | | | ITEM | | | | | | ITEM | | | | | | | | | | | | | +--------------+ | +---------------------+ | +-------------+ | +------------------+-------------------------+-----------------+ \endverbatim * * A typical use case is an Abscissa axis with long labels: \verbatim 2 -| | 1 -| | 0 -+------------------------------------ | | | | | Monday Tuesday Wednesday Thursday Friday \endverbatim * The last letters of the word "Friday" would have been * cut off in previous versions of KChart - that is * if you did not call KChart::Chart::setGlobalLeading(). * * Now the word will be shown completely because there * is an auto-spacer-item taking care for the additional * space needed in the lower/right corner. */ class KCHART_EXPORT AutoSpacerLayoutItem : public AbstractLayoutItem { public: AutoSpacerLayoutItem( bool layoutIsAtTopPosition, QHBoxLayout *rightLeftLayout, bool layoutIsAtLeftPosition, QVBoxLayout *topBottomLayout ); Qt::Orientations expandingDirections() const override; QRect geometry() const override; bool isEmpty() const override; QSize maximumSize() const override; QSize minimumSize() const override; void setGeometry( const QRect& r ) override; QSize sizeHint() const override; void paint( QPainter* ) override; private: QRect mRect; bool mLayoutIsAtTopPosition; QHBoxLayout *mRightLeftLayout; bool mLayoutIsAtLeftPosition; QVBoxLayout *mTopBottomLayout; mutable QBrush mCommonBrush; mutable QSize mCachedSize; }; } #endif /* KCHARTLAYOUTITEMS_H */ diff --git a/src/KChart/KChartLegend.cpp b/src/KChart/KChartLegend.cpp index 3bb0a03..46c3838 100644 --- a/src/KChart/KChartLegend.cpp +++ b/src/KChart/KChartLegend.cpp @@ -1,1259 +1,1251 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLegend.h" #include "KChartLegend_p.h" #include #include #include #include #include "KTextDocument.h" #include #include "KChartLayoutItems.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; Legend::Private::Private() : referenceArea( nullptr ), position( Position::East ), alignment( Qt::AlignCenter ), textAlignment( Qt::AlignCenter ), relativePosition( RelativePosition() ), orientation( Qt::Vertical ), order( Qt::AscendingOrder ), showLines( false ), titleText( QObject::tr( "Legend" ) ), spacing( 1 ), useAutomaticMarkerSize( true ), legendStyle( MarkersOnly ) { // By default we specify a simple, hard point as the 'relative' position's ref. point, // since we can not be sure that there will be any parent specified for the legend. relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) ); relativePosition.setReferencePosition( Position::NorthWest ); relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft ); relativePosition.setHorizontalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) ); relativePosition.setVerticalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) ); } Legend::Private::~Private() { // this bloc left empty intentionally } #define d d_func() Legend::Legend( QWidget* parent ) : AbstractAreaWidget( new Private(), parent ) { d->referenceArea = parent; init(); } Legend::Legend( AbstractDiagram* diagram, QWidget* parent ) : AbstractAreaWidget( new Private(), parent ) { d->referenceArea = parent; init(); setDiagram( diagram ); } Legend::~Legend() { emit destroyedLegend( this ); } void Legend::init() { setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); d->layout = new QGridLayout( this ); d->layout->setContentsMargins( 2, 2, 2, 2 ); d->layout->setSpacing( d->spacing ); const Measure normalFontSizeTitle( 12, KChartEnums::MeasureCalculationModeAbsolute ); const Measure normalFontSizeLabels( 10, KChartEnums::MeasureCalculationModeAbsolute ); const Measure minimalFontSize( 4, KChartEnums::MeasureCalculationModeAbsolute ); TextAttributes textAttrs; textAttrs.setPen( QPen( Qt::black ) ); textAttrs.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Normal, false ) ); textAttrs.setFontSize( normalFontSizeLabels ); textAttrs.setMinimalFontSize( minimalFontSize ); setTextAttributes( textAttrs ); TextAttributes titleTextAttrs; titleTextAttrs.setPen( QPen( Qt::black ) ); titleTextAttrs.setFont( QFont( QLatin1String( "helvetica" ), 12, QFont::Bold, false ) ); titleTextAttrs.setFontSize( normalFontSizeTitle ); titleTextAttrs.setMinimalFontSize( minimalFontSize ); setTitleTextAttributes( titleTextAttrs ); FrameAttributes frameAttrs; frameAttrs.setVisible( true ); frameAttrs.setPen( QPen( Qt::black ) ); frameAttrs.setPadding( 1 ); setFrameAttributes( frameAttrs ); d->position = Position::NorthEast; d->alignment = Qt::AlignCenter; } QSize Legend::minimumSizeHint() const { return sizeHint(); } //#define DEBUG_LEGEND_PAINT QSize Legend::sizeHint() const { #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::sizeHint() started"; #endif Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) { paintItem->sizeHint(); } return AbstractAreaWidget::sizeHint(); } void Legend::needSizeHint() { buildLegend(); } void Legend::resizeLayout( const QSize& size ) { #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::resizeLayout started"; #endif if ( d->layout ) { d->reflowHDatasetItems( this ); d->layout->setGeometry( QRect(QPoint( 0,0 ), size) ); activateTheLayout(); } #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::resizeLayout done"; #endif } void Legend::activateTheLayout() { if ( d->layout && d->layout->parent() ) { d->layout->activate(); } } void Legend::setLegendStyle( LegendStyle style ) { if ( d->legendStyle == style ) { return; } d->legendStyle = style; setNeedRebuild(); } Legend::LegendStyle Legend::legendStyle() const { return d->legendStyle; } -/** - * Creates an exact copy of this legend. - */ Legend* Legend::clone() const { Legend* legend = new Legend( new Private( *d ), nullptr ); legend->setTextAttributes( textAttributes() ); legend->setTitleTextAttributes( titleTextAttributes() ); legend->setFrameAttributes( frameAttributes() ); legend->setUseAutomaticMarkerSize( useAutomaticMarkerSize() ); legend->setPosition( position() ); legend->setAlignment( alignment() ); legend->setTextAlignment( textAlignment() ); legend->setLegendStyle( legendStyle() ); return legend; } bool Legend::compare( const Legend* other ) const { if ( other == this ) { return true; } if ( !other ) { return false; } return ( AbstractAreaBase::compare( other ) ) && (isVisible() == other->isVisible()) && (position() == other->position()) && (alignment() == other->alignment())&& (textAlignment() == other->textAlignment())&& (floatingPosition() == other->floatingPosition()) && (orientation() == other->orientation())&& (showLines() == other->showLines())&& (texts() == other->texts())&& (brushes() == other->brushes())&& (pens() == other->pens())&& (markerAttributes() == other->markerAttributes())&& (useAutomaticMarkerSize() == other->useAutomaticMarkerSize()) && (textAttributes() == other->textAttributes()) && (titleText() == other->titleText())&& (titleTextAttributes() == other->titleTextAttributes()) && (spacing() == other->spacing()) && (legendStyle() == other->legendStyle()); } void Legend::paint( QPainter* painter ) { #ifdef DEBUG_LEGEND_PAINT qDebug() << "entering Legend::paint( QPainter* painter )"; #endif if ( !diagram() ) { return; } activateTheLayout(); Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) { paintItem->paint( painter ); } #ifdef DEBUG_LEGEND_PAINT qDebug() << "leaving Legend::paint( QPainter* painter )"; #endif } uint Legend::datasetCount() const { int modelLabelsCount = 0; Q_FOREACH ( DiagramObserver* observer, d->observers ) { AbstractDiagram* diagram = observer->diagram(); Q_ASSERT( diagram->datasetLabels().count() == diagram->datasetBrushes().count() ); modelLabelsCount += diagram->datasetLabels().count(); } return modelLabelsCount; } void Legend::setReferenceArea( const QWidget* area ) { if ( area == d->referenceArea ) { return; } d->referenceArea = area; setNeedRebuild(); } const QWidget* Legend::referenceArea() const { return d->referenceArea ? d->referenceArea : qobject_cast< const QWidget* >( parent() ); } AbstractDiagram* Legend::diagram() const { if ( d->observers.isEmpty() ) { return nullptr; } return d->observers.first()->diagram(); } DiagramList Legend::diagrams() const { DiagramList list; for ( int i = 0; i < d->observers.size(); ++i ) { list << d->observers.at(i)->diagram(); } return list; } ConstDiagramList Legend::constDiagrams() const { ConstDiagramList list; for ( int i = 0; i < d->observers.size(); ++i ) { list << d->observers.at(i)->diagram(); } return list; } void Legend::addDiagram( AbstractDiagram* newDiagram ) { if ( newDiagram ) { DiagramObserver* observer = new DiagramObserver( newDiagram, this ); DiagramObserver* oldObs = d->findObserverForDiagram( newDiagram ); if ( oldObs ) { delete oldObs; d->observers[ d->observers.indexOf( oldObs ) ] = observer; } else { d->observers.append( observer ); } connect( observer, SIGNAL(diagramAboutToBeDestroyed(AbstractDiagram*)), SLOT(resetDiagram(AbstractDiagram*))); connect( observer, SIGNAL(diagramDataChanged(AbstractDiagram*)), SLOT(setNeedRebuild())); connect( observer, SIGNAL(diagramDataHidden(AbstractDiagram*)), SLOT(setNeedRebuild())); connect( observer, SIGNAL(diagramAttributesChanged(AbstractDiagram*)), SLOT(setNeedRebuild())); setNeedRebuild(); } } void Legend::removeDiagram( AbstractDiagram* oldDiagram ) { int datasetBrushOffset = 0; QList< AbstractDiagram * > diagrams = this->diagrams(); for ( int i = 0; i datasetBrushes().count(); i++ ) { d->brushes.remove(datasetBrushOffset + i); d->texts.remove(datasetBrushOffset + i); } for ( int i = 0; i < oldDiagram->datasetPens().count(); i++ ) { d->pens.remove(datasetBrushOffset + i); } break; } datasetBrushOffset += diagrams.at(i)->datasetBrushes().count(); } if ( oldDiagram ) { DiagramObserver *oldObs = d->findObserverForDiagram( oldDiagram ); if ( oldObs ) { delete oldObs; d->observers.removeAt( d->observers.indexOf( oldObs ) ); } setNeedRebuild(); } } void Legend::removeDiagrams() { // removeDiagram() may change the d->observers list. So, build up the list of // diagrams to remove first and then remove them one by one. QList< AbstractDiagram * > diagrams; for ( int i = 0; i < d->observers.size(); ++i ) { diagrams.append( d->observers.at( i )->diagram() ); } for ( int i = 0; i < diagrams.count(); ++i ) { removeDiagram( diagrams[ i ] ); } } void Legend::replaceDiagram( AbstractDiagram* newDiagram, AbstractDiagram* oldDiagram ) { AbstractDiagram* old = oldDiagram; if ( !d->observers.isEmpty() && !old ) { old = d->observers.first()->diagram(); if ( !old ) { d->observers.removeFirst(); // first entry had a 0 diagram } } if ( old ) { removeDiagram( old ); } if ( newDiagram ) { addDiagram( newDiagram ); } } uint Legend::dataSetOffset( AbstractDiagram* diagram ) { uint offset = 0; for ( int i = 0; i < d->observers.count(); ++i ) { if ( d->observers.at(i)->diagram() == diagram ) { return offset; } AbstractDiagram* diagram = d->observers.at(i)->diagram(); if ( !diagram->model() ) { continue; } offset = offset + diagram->model()->columnCount(); } return offset; } void Legend::setDiagram( AbstractDiagram* newDiagram ) { replaceDiagram( newDiagram ); } void Legend::resetDiagram( AbstractDiagram* oldDiagram ) { removeDiagram( oldDiagram ); } void Legend::setVisible( bool visible ) { // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends // on the visibility of the parent. QWidget::setVisible( visible ); emitPositionChanged(); } void Legend::setNeedRebuild() { buildLegend(); sizeHint(); } void Legend::setPosition( Position position ) { if ( d->position == position ) { return; } d->position = position; emitPositionChanged(); } void Legend::emitPositionChanged() { emit positionChanged( this ); emit propertiesChanged(); } Position Legend::position() const { return d->position; } void Legend::setAlignment( Qt::Alignment alignment ) { if ( d->alignment == alignment ) { return; } d->alignment = alignment; emitPositionChanged(); } Qt::Alignment Legend::alignment() const { return d->alignment; } void Legend::setTextAlignment( Qt::Alignment alignment ) { if ( d->textAlignment == alignment ) { return; } d->textAlignment = alignment; emitPositionChanged(); } Qt::Alignment Legend::textAlignment() const { return d->textAlignment; } void Legend::setLegendSymbolAlignment( Qt::Alignment alignment ) { if ( d->legendLineSymbolAlignment == alignment ) { return; } d->legendLineSymbolAlignment = alignment; emitPositionChanged(); } Qt::Alignment Legend::legendSymbolAlignment() const { return d->legendLineSymbolAlignment ; } void Legend::setFloatingPosition( const RelativePosition& relativePosition ) { d->position = Position::Floating; if ( d->relativePosition != relativePosition ) { d->relativePosition = relativePosition; emitPositionChanged(); } } const RelativePosition Legend::floatingPosition() const { return d->relativePosition; } void Legend::setOrientation( Qt::Orientation orientation ) { if ( d->orientation == orientation ) { return; } d->orientation = orientation; setNeedRebuild(); emitPositionChanged(); } Qt::Orientation Legend::orientation() const { return d->orientation; } void Legend::setSortOrder( Qt::SortOrder order ) { if ( d->order == order ) { return; } d->order = order; setNeedRebuild(); emitPositionChanged(); } Qt::SortOrder Legend::sortOrder() const { return d->order; } void Legend::setShowLines( bool legendShowLines ) { if ( d->showLines == legendShowLines ) { return; } d->showLines = legendShowLines; setNeedRebuild(); emitPositionChanged(); } bool Legend::showLines() const { return d->showLines; } void Legend::setUseAutomaticMarkerSize( bool useAutomaticMarkerSize ) { d->useAutomaticMarkerSize = useAutomaticMarkerSize; setNeedRebuild(); emitPositionChanged(); } bool Legend::useAutomaticMarkerSize() const { return d->useAutomaticMarkerSize; } -/** - \brief Removes all legend texts that might have been set by setText. - - This resets the Legend to default behaviour: Texts are created automatically. -*/ void Legend::resetTexts() { if ( !d->texts.count() ) { return; } d->texts.clear(); setNeedRebuild(); } void Legend::setText( uint dataset, const QString& text ) { if ( d->texts[ dataset ] == text ) { return; } d->texts[ dataset ] = text; setNeedRebuild(); } QString Legend::text( uint dataset ) const { if ( d->texts.find( dataset ) != d->texts.end() ) { return d->texts[ dataset ]; } else { return d->modelLabels[ dataset ]; } } const QMap Legend::texts() const { return d->texts; } void Legend::setColor( uint dataset, const QColor& color ) { if ( d->brushes[ dataset ] != color ) { d->brushes[ dataset ] = color; setNeedRebuild(); update(); } } void Legend::setBrush( uint dataset, const QBrush& brush ) { if ( d->brushes[ dataset ] != brush ) { d->brushes[ dataset ] = brush; setNeedRebuild(); update(); } } QBrush Legend::brush( uint dataset ) const { if ( d->brushes.contains( dataset ) ) { return d->brushes[ dataset ]; } else { return d->modelBrushes[ dataset ]; } } const QMap Legend::brushes() const { return d->brushes; } void Legend::setBrushesFromDiagram( AbstractDiagram* diagram ) { bool changed = false; QList datasetBrushes = diagram->datasetBrushes(); for ( int i = 0; i < datasetBrushes.count(); i++ ) { if ( d->brushes[ i ] != datasetBrushes[ i ] ) { d->brushes[ i ] = datasetBrushes[ i ]; changed = true; } } if ( changed ) { setNeedRebuild(); update(); } } void Legend::setPen( uint dataset, const QPen& pen ) { if ( d->pens[dataset] == pen ) { return; } d->pens[dataset] = pen; setNeedRebuild(); update(); } QPen Legend::pen( uint dataset ) const { if ( d->pens.find( dataset ) != d->pens.end() ) { return d->pens[ dataset ]; } else { return d->modelPens[ dataset ]; } } const QMap Legend::pens() const { return d->pens; } void Legend::setMarkerAttributes( uint dataset, const MarkerAttributes& markerAttributes ) { if ( d->markerAttributes[dataset] == markerAttributes ) { return; } d->markerAttributes[ dataset ] = markerAttributes; setNeedRebuild(); update(); } MarkerAttributes Legend::markerAttributes( uint dataset ) const { if ( d->markerAttributes.find( dataset ) != d->markerAttributes.end() ) { return d->markerAttributes[ dataset ]; } else if ( static_cast( d->modelMarkers.count() ) > dataset ) { return d->modelMarkers[ dataset ]; } else { return MarkerAttributes(); } } const QMap Legend::markerAttributes() const { return d->markerAttributes; } void Legend::setTextAttributes( const TextAttributes &a ) { if ( d->textAttributes == a ) { return; } d->textAttributes = a; setNeedRebuild(); } TextAttributes Legend::textAttributes() const { return d->textAttributes; } void Legend::setTitleText( const QString& text ) { if ( d->titleText == text ) { return; } d->titleText = text; setNeedRebuild(); } QString Legend::titleText() const { return d->titleText; } void Legend::setTitleTextAttributes( const TextAttributes &a ) { if ( d->titleTextAttributes == a ) { return; } d->titleTextAttributes = a; setNeedRebuild(); } TextAttributes Legend::titleTextAttributes() const { return d->titleTextAttributes; } void Legend::forceRebuild() { #ifdef DEBUG_LEGEND_PAINT qDebug() << "entering Legend::forceRebuild()"; #endif buildLegend(); #ifdef DEBUG_LEGEND_PAINT qDebug() << "leaving Legend::forceRebuild()"; #endif } void Legend::setSpacing( uint space ) { if ( d->spacing == space && d->layout->spacing() == int( space ) ) { return; } d->spacing = space; d->layout->setSpacing( space ); setNeedRebuild(); } uint Legend::spacing() const { return d->spacing; } void Legend::setDefaultColors() { Palette pal = Palette::defaultPalette(); for ( int i = 0; i < pal.size(); i++ ) { setBrush( i, pal.getBrush( i ) ); } } void Legend::setRainbowColors() { Palette pal = Palette::rainbowPalette(); for ( int i = 0; i < pal.size(); i++ ) { setBrush( i, pal.getBrush( i ) ); } } void Legend::setSubduedColors( bool ordered ) { Palette pal = Palette::subduedPalette(); if ( ordered ) { for ( int i = 0; i < pal.size(); i++ ) { setBrush( i, pal.getBrush( i ) ); } } else { static const int s_subduedColorsCount = 18; Q_ASSERT( pal.size() >= s_subduedColorsCount ); static const int order[ s_subduedColorsCount ] = { 0, 5, 10, 15, 2, 7, 12, 17, 4, 9, 14, 1, 6, 11, 16, 3, 8, 13 }; for ( int i = 0; i < s_subduedColorsCount; i++ ) { setBrush( i, pal.getBrush( order[i] ) ); } } } void Legend::resizeEvent( QResizeEvent * event ) { Q_UNUSED( event ); #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::resizeEvent() called"; #endif forceRebuild(); sizeHint(); QTimer::singleShot( 0, this, SLOT(emitPositionChanged()) ); } void Legend::Private::fetchPaintOptions( Legend *q ) { modelLabels.clear(); modelBrushes.clear(); modelPens.clear(); modelMarkers.clear(); // retrieve the diagrams' settings for all non-hidden datasets for ( int i = 0; i < observers.size(); ++i ) { const AbstractDiagram* diagram = observers.at( i )->diagram(); if ( !diagram ) { continue; } const QStringList diagramLabels = diagram->datasetLabels(); const QList diagramBrushes = diagram->datasetBrushes(); const QList diagramPens = diagram->datasetPens(); const QList diagramMarkers = diagram->datasetMarkers(); const bool ascend = q->sortOrder() == Qt::AscendingOrder; int dataset = ascend ? 0 : diagramLabels.count() - 1; const int end = ascend ? diagramLabels.count() : -1; for ( ; dataset != end; dataset += ascend ? 1 : -1 ) { if ( diagram->isHidden( dataset ) || q->datasetIsHidden( dataset ) ) { continue; } modelLabels += diagramLabels[ dataset ]; modelBrushes += diagramBrushes[ dataset ]; modelPens += diagramPens[ dataset ]; modelMarkers += diagramMarkers[ dataset ]; } } Q_ASSERT( modelLabels.count() == modelBrushes.count() ); } QSizeF Legend::Private::markerSize( Legend *q, int dataset, qreal fontHeight ) const { QSizeF suppliedSize = q->markerAttributes( dataset ).markerSize(); if ( q->useAutomaticMarkerSize() || !suppliedSize.isValid() ) { return QSizeF( fontHeight, fontHeight ); } else { return suppliedSize; } } QSizeF Legend::Private::maxMarkerSize( Legend *q, qreal fontHeight ) const { QSizeF ret( 1.0, 1.0 ); if ( q->legendStyle() != LinesOnly ) { for ( int dataset = 0; dataset < modelLabels.count(); ++dataset ) { ret = ret.expandedTo( markerSize( q, dataset, fontHeight ) ); } } return ret; } HDatasetItem::HDatasetItem() : markerLine(nullptr), label(nullptr), separatorLine(nullptr), spacer(nullptr) {} static void updateToplevelLayout(QWidget *w) { while ( w ) { if ( w->isTopLevel() ) { // The null check has proved necessary during destruction of the Legend / Chart if ( w->layout() ) { w->layout()->update(); } break; } else { w = qobject_cast< QWidget * >( w->parent() ); Q_ASSERT( w ); } } } void Legend::buildLegend() { /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider line between title and dataset items, row two for each item: line, marker, text label and separator line in that order. In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row) and (second row) line, marker, text label each. */ d->destroyOldLayout(); if ( orientation() == Qt::Vertical ) { d->layout->setColumnStretch( 6, 1 ); } else { d->layout->setColumnStretch( 6, 0 ); } d->fetchPaintOptions( this ); const KChartEnums::MeasureOrientation measureOrientation = orientation() == Qt::Vertical ? KChartEnums::MeasureOrientationMinimum : KChartEnums::MeasureOrientationHorizontal; // legend caption if ( !titleText().isEmpty() && titleTextAttributes().isVisible() ) { TextLayoutItem* titleItem = new TextLayoutItem( titleText(), titleTextAttributes(), referenceArea(), measureOrientation, d->textAlignment ); titleItem->setParentWidget( this ); d->paintItems << titleItem; d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter ); // The line between the title and the legend items, if any. if ( showLines() && d->modelLabels.count() ) { HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem; d->paintItems << lineItem; d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter ); } } qreal fontHeight = textAttributes().calculatedFontSize( referenceArea(), measureOrientation ); { QFont tmpFont = textAttributes().font(); tmpFont.setPointSizeF( fontHeight ); if ( GlobalMeasureScaling::paintDevice() ) { fontHeight = QFontMetricsF( tmpFont, GlobalMeasureScaling::paintDevice() ).height(); } else { fontHeight = QFontMetricsF( tmpFont ).height(); } } const QSizeF maxMarkerSize = d->maxMarkerSize( this, fontHeight ); // If we show a marker on a line, we paint it after 8 pixels // of the line have been painted. This allows to see the line style // at the right side of the marker without the line needing to // be too long. // (having the marker in the middle of the line would require longer lines) const int lineLengthLeftOfMarker = 8; int maxLineLength = 18; { bool hasComplexPenStyle = false; for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) { const QPen pn = pen( dataset ); const Qt::PenStyle ps = pn.style(); if ( ps != Qt::NoPen ) { maxLineLength = qMin( pn.width() * 18, maxLineLength ); if ( ps != Qt::SolidLine ) { hasComplexPenStyle = true; } } } if ( legendStyle() != LinesOnly ) { if ( hasComplexPenStyle ) maxLineLength += lineLengthLeftOfMarker; maxLineLength += int( maxMarkerSize.width() ); } } // for all datasets: add (line)marker items and text items to the layout; // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) { const int vLayoutRow = 2 + dataset * 2; HDatasetItem dsItem; // It is possible to set the marker brush through markerAttributes as well as // the dataset brush set in the diagram - the markerAttributes have higher precedence. MarkerAttributes markerAttrs = markerAttributes( dataset ); markerAttrs.setMarkerSize( d->markerSize( this, dataset, fontHeight ) ); const QBrush markerBrush = markerAttrs.markerColor().isValid() ? QBrush( markerAttrs.markerColor() ) : brush( dataset ); switch ( legendStyle() ) { case MarkersOnly: dsItem.markerLine = new MarkerLayoutItem( diagram(), markerAttrs, markerBrush, markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter ); break; case LinesOnly: dsItem.markerLine = new LineLayoutItem( diagram(), maxLineLength, pen( dataset ), d->legendLineSymbolAlignment, Qt::AlignCenter ); break; case MarkersAndLines: dsItem.markerLine = new LineWithMarkerLayoutItem( diagram(), maxLineLength, pen( dataset ), lineLengthLeftOfMarker, markerAttrs, markerBrush, markerAttrs.pen(), Qt::AlignCenter ); break; default: Q_ASSERT( false ); } dsItem.label = new TextLayoutItem( text( dataset ), textAttributes(), referenceArea(), measureOrientation, d->textAlignment ); dsItem.label->setParentWidget( this ); // horizontal layout is deferred to flowDatasetItems() if ( orientation() == Qt::Horizontal ) { d->hLayoutDatasets << dsItem; continue; } // (actual) vertical layout here if ( dsItem.markerLine ) { d->layout->addItem( dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter ); d->paintItems << dsItem.markerLine; } d->layout->addItem( dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter ); d->paintItems << dsItem.label; // horizontal separator line, only between items if ( showLines() && dataset != d->modelLabels.count() - 1 ) { HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem; d->layout->addItem( lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter ); d->paintItems << lineItem; } } if ( orientation() == Qt::Horizontal ) { d->flowHDatasetItems( this ); } // vertical line (only in vertical mode) if ( orientation() == Qt::Vertical && showLines() && d->modelLabels.count() ) { VerticalLineLayoutItem* lineItem = new VerticalLineLayoutItem; d->paintItems << lineItem; d->layout->addItem( lineItem, 2, 2, d->modelLabels.count() * 2, 1 ); } updateToplevelLayout( this ); emit propertiesChanged(); #ifdef DEBUG_LEGEND_PAINT qDebug() << "leaving Legend::buildLegend()"; #endif } int HDatasetItem::height() const { return qMax( markerLine->sizeHint().height(), label->sizeHint().height() ); } void Legend::Private::reflowHDatasetItems( Legend *q ) { if (hLayoutDatasets.isEmpty()) { return; } paintItems.clear(); // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the // caption and line under the caption! Those are easily identified because they aren't QLayouts. for ( int i = layout->count() - 1; i >= 0; i-- ) { QLayoutItem *const item = layout->itemAt( i ); QLayout *const hbox = item->layout(); if ( !hbox ) { AbstractLayoutItem *alItem = dynamic_cast< AbstractLayoutItem * >( item ); Q_ASSERT( alItem ); paintItems << alItem; continue; } Q_ASSERT( dynamic_cast< QHBoxLayout * >( hbox ) ); layout->takeAt( i ); // detach children so they aren't deleted with the parent for ( int j = hbox->count() - 1; j >= 0; j-- ) { hbox->takeAt( j ); } delete hbox; } flowHDatasetItems( q ); } // this works pretty much like flow layout for text, and it is only applicable to dataset items // laid out horizontally void Legend::Private::flowHDatasetItems( Legend *q ) { const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint() const int allowedWidth = q->areaGeometry().width(); QHBoxLayout *currentLine = new QHBoxLayout; int columnSpan = 5; int mainLayoutColumn = 0; int row = 0; if ( !titleText.isEmpty() && titleTextAttributes.isVisible() ) { ++row; if (q->showLines()){ ++row; } } layout->addItem( currentLine, row, mainLayoutColumn, /*rowSpan*/1 , columnSpan, Qt::AlignLeft | Qt::AlignVCenter ); mainLayoutColumn += columnSpan; for ( int dataset = 0; dataset < hLayoutDatasets.size(); dataset++ ) { HDatasetItem *hdsItem = &hLayoutDatasets[ dataset ]; bool spacerUsed = false; bool separatorUsed = false; if ( !currentLine->isEmpty() ) { const int separatorWidth = ( q->showLines() ? separatorLineWidth : 0 ) + q->spacing(); const int payloadWidth = hdsItem->markerLine->sizeHint().width() + hdsItem->label->sizeHint().width(); if ( currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth ) { // too wide, "line break" #ifdef DEBUG_LEGEND_PAINT qDebug() << Q_FUNC_INFO << "break" << mainLayoutColumn << currentLine->sizeHint().width() << currentLine->sizeHint().width() + separatorWidth + payloadWidth << allowedWidth; #endif currentLine = new QHBoxLayout; layout->addItem( currentLine, row, mainLayoutColumn, /*rowSpan*/1 , columnSpan, Qt::AlignLeft | Qt::AlignVCenter ); mainLayoutColumn += columnSpan; } else { // > 1 dataset item in line, put spacing and maybe a separator between them if ( !hdsItem->spacer ) { hdsItem->spacer = new QSpacerItem( q->spacing(), 1 ); } currentLine->addItem( hdsItem->spacer ); spacerUsed = true; if ( q->showLines() ) { if ( !hdsItem->separatorLine ) { hdsItem->separatorLine = new VerticalLineLayoutItem; } paintItems << hdsItem->separatorLine; currentLine->addItem( hdsItem->separatorLine ); separatorUsed = true; } } } // those have no parents in the current layout, so they wouldn't get cleaned up otherwise if ( !spacerUsed ) { delete hdsItem->spacer; hdsItem->spacer = nullptr; } if ( !separatorUsed ) { delete hdsItem->separatorLine; hdsItem->separatorLine = nullptr; } currentLine->addItem( hdsItem->markerLine ); paintItems << hdsItem->markerLine; currentLine->addItem( hdsItem->label ); paintItems << hdsItem->label; } } bool Legend::hasHeightForWidth() const { // this is better than using orientation() because, for layout purposes, we're not height-for-width // *yet* before buildLegend() has been called, and the layout logic might get upset if we say // something that will only be true in the future return !d->hLayoutDatasets.isEmpty(); } int Legend::heightForWidth( int width ) const { if ( d->hLayoutDatasets.isEmpty() ) { return -1; } int ret = 0; // space for caption and line under caption (if any) for (int i = 0; i < 2; i++) { if ( QLayoutItem *item = d->layout->itemAtPosition( i, 0 ) ) { ret += item->sizeHint().height(); } } const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint() int currentLineWidth = 0; int currentLineHeight = 0; Q_FOREACH( const HDatasetItem &hdsItem, d->hLayoutDatasets ) { const int payloadWidth = hdsItem.markerLine->sizeHint().width() + hdsItem.label->sizeHint().width(); if ( !currentLineWidth ) { // first iteration currentLineWidth = payloadWidth; } else { const int separatorWidth = ( showLines() ? separatorLineWidth : 0 ) + spacing(); currentLineWidth += separatorWidth + payloadWidth; if ( currentLineWidth > width ) { // too wide, "line break" #ifdef DEBUG_LEGEND_PAINT qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth << currentLineWidth + separatorWidth + payloadWidth << width; #endif ret += currentLineHeight + spacing(); currentLineWidth = payloadWidth; currentLineHeight = 0; } } currentLineHeight = qMax( currentLineHeight, hdsItem.height() ); } ret += currentLineHeight; // one less spacings than lines return ret; } void Legend::Private::destroyOldLayout() { // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items // (it isn't documented that QLayoutItems delete their children) for ( int i = layout->count() - 1; i >= 0; i-- ) { delete layout->takeAt( i ); } Q_ASSERT( !layout->count() ); hLayoutDatasets.clear(); paintItems.clear(); } void Legend::setHiddenDatasets( const QList hiddenDatasets ) { d->hiddenDatasets = hiddenDatasets; } const QList Legend::hiddenDatasets() const { return d->hiddenDatasets; } void Legend::setDatasetHidden( uint dataset, bool hidden ) { if ( hidden && !d->hiddenDatasets.contains( dataset ) ) { d->hiddenDatasets.append( dataset ); } else if ( !hidden && d->hiddenDatasets.contains( dataset ) ) { d->hiddenDatasets.removeAll( dataset ); } } bool Legend::datasetIsHidden( uint dataset ) const { return d->hiddenDatasets.contains( dataset ); } diff --git a/src/KChart/KChartLegend.h b/src/KChart/KChartLegend.h index a79108d..c5c0549 100644 --- a/src/KChart/KChartLegend.h +++ b/src/KChart/KChartLegend.h @@ -1,408 +1,418 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTLEGEND_H #define KCHARTLEGEND_H #include "KChartAbstractAreaWidget.h" #include "KChartPosition.h" #include "KChartMarkerAttributes.h" class QTextTable; namespace KChart { class AbstractDiagram; typedef QList DiagramList; typedef QList ConstDiagramList; /** * @brief Legend defines the interface for the legend drawing class. * * Legend is the class for drawing legends for all kinds of diagrams ("chart types"). * * Legend is drawn on chart level, not per diagram, but you can have more than one * legend per chart, using KChart::Chart::addLegend(). * * \note Legend is different from all other classes ofd KChart, since it can be * displayed outside of the Chart's area. If you want to, you can embedd the legend * into your own widget, or into another part of a bigger layout, into which you might * have inserted the Chart. * * On the other hand, please note that you MUST call Chart::addLegend to get your * legend positioned into the correct place of your chart - if you want to have * the legend shown inside of the chart (that's probably true for most cases). */ class KCHART_EXPORT Legend : public AbstractAreaWidget { Q_OBJECT Q_DISABLE_COPY( Legend ) KCHART_DECLARE_PRIVATE_DERIVED_QWIDGET( Legend ) public: explicit Legend( QWidget* parent = nullptr ); explicit Legend( KChart::AbstractDiagram* diagram, QWidget* parent = nullptr ); virtual ~Legend(); enum LegendStyle { MarkersOnly = 0, LinesOnly = 1, MarkersAndLines = 2 }; void setLegendStyle( LegendStyle style ); LegendStyle legendStyle() const; + + /** + * Creates an exact copy of this legend. + */ virtual Legend * clone() const; /** * Returns true if both legends have the same settings. */ bool compare( const Legend* other ) const; void resizeEvent( QResizeEvent * event ) override; // TODO: should be protected virtual void paint( QPainter* painter ) override; void setVisible( bool visible ) override; /** Specifies the reference area for font size of title text, and for font size of the item texts, IF automatic area detection is set. \note This parameter is ignored, if the Measure given for setTitleTextAttributes (or setTextAttributes, resp.) is not specifying automatic area detection. If no reference area is specified, but automatic area detection is set, then the size of the legend's parent widget will be used. \sa KChart::Measure, KChartEnums::MeasureCalculationMode */ void setReferenceArea( const QWidget* area ); /** Returns the reference area, that is used for font size of title text, and for font size of the item texts, IF automatic area detection is set. \sa setReferenceArea */ const QWidget* referenceArea() const; /** * The first diagram of the legend or 0 if there was none added to the legend. * @return The first diagram of the legend or 0. * * \sa diagrams, addDiagram, removeDiagram, removeDiagrams, replaceDiagram, setDiagram */ KChart::AbstractDiagram* diagram() const; /** * The list of all diagrams associated with the legend. * @return The list of all diagrams associated with the legend. * * \sa diagram, addDiagram, removeDiagram, removeDiagrams, replaceDiagram, setDiagram */ DiagramList diagrams() const; /** * @return The list of diagrams associated with this legend. */ ConstDiagramList constDiagrams() const; /** * Add the given diagram to the legend. * @param newDiagram The diagram to add. * * \sa diagram, diagrams, removeDiagram, removeDiagrams, replaceDiagram, setDiagram */ void addDiagram( KChart::AbstractDiagram* newDiagram ); /** * Removes the diagram from the legend's list of diagrams. * * \sa diagram, diagrams, addDiagram, removeDiagrams, replaceDiagram, setDiagram */ void removeDiagram( KChart::AbstractDiagram* oldDiagram ); /** * Removes all diagrams from the legend's list of diagrams. * * \sa diagram, diagrams, addDiagram, removeDiagram, replaceDiagram, setDiagram */ void removeDiagrams(); /** * Replaces the old diagram, or appends the * new diagram, it there is none yet. * * @param newDiagram The diagram to be used instead of the old one. * If this parameter is zero, the first diagram will just be removed. * * @param oldDiagram The diagram to be removed by the new one. This * diagram will be deleted automatically. If the parameter is omitted, * the very first diagram will be replaced. In case, there was no * diagram yet, the new diagram will just be added. * * \sa diagram, diagrams, addDiagram, removeDiagram, removeDiagrams, setDiagram */ void replaceDiagram( KChart::AbstractDiagram* newDiagram, KChart::AbstractDiagram* oldDiagram = nullptr ); - /** + /** * Returns the offset of the first dataset of \c diagram. * */ uint dataSetOffset( KChart::AbstractDiagram* diagram ); /** * @brief A convenience method doing the same as replaceDiagram( newDiagram, 0 ); * * Replaces the first diagram by the given diagram. * If the legend's list of diagram is empty the given diagram is added to the list. * * \sa diagram, diagrams, addDiagram, removeDiagram, removeDiagrams, replaceDiagram */ void setDiagram( KChart::AbstractDiagram* newDiagram ); /** * \brief Specify the position of a non-floating legend. * * Use setFloatingPosition to set position and alignment * if your legend is floating. * * \sa setAlignment, setFloatingPosition */ void setPosition( Position position ); /** * Returns the position of a non-floating legend. * \sa setPosition */ Position position() const; /** * \brief Specify the alignment of a non-floating legend. * * Use setFloatingPosition to set position and alignment * if your legend is floating. * * \sa alignment, setPosition, setFloatingPosition */ void setAlignment( Qt::Alignment ); /** * Returns the alignment of a non-floating legend. * \sa setAlignment */ Qt::Alignment alignment() const; /** * \brief Specify the alignment of the text elements within the legend * * \sa textAlignment() */ void setTextAlignment( Qt::Alignment ); /** * \brief Returns the alignment used while rendering text elements within the legend. * * \sa setTextAlignment() */ Qt::Alignment textAlignment() const; /** * \brief Specify the alignment of the legend symbol( alignment of Legend::LinesOnly) * within the legend * * \sa legendSymbolAlignment() */ void setLegendSymbolAlignment(Qt::Alignment); /** * \brief Returns the alignment used while drawing legend symbol(alignment of Legend::LinesOnly) * within the legend. * * \sa setLegendSymbolAlignment() */ Qt::Alignment legendSymbolAlignment() const; /** * \brief Specify the position and alignment of a floating legend. * * Use setPosition and setAlignment to set position and alignment * if your legend is non-floating. * * \note When setFloatingPosition is called, the Legend's position value is set to * KChart::Position::Floating automatically. * * If your Chart is pointed to by m_chart, your could have the floating legend * aligned exactly to the chart's coordinate plane's top-right corner * with the following commands: \verbatim KChart::RelativePosition relativePosition; relativePosition.setReferenceArea( m_chart->coordinatePlane() ); relativePosition.setReferencePosition( Position::NorthEast ); relativePosition.setAlignment( Qt::AlignTop | Qt::AlignRight ); relativePosition.setHorizontalPadding( KChart::Measure( -1.0, KChartEnums::MeasureCalculationModeAbsolute ) ); relativePosition.setVerticalPadding( KChart::Measure( 0.0, KChartEnums::MeasureCalculationModeAbsolute ) ); m_legend->setFloatingPosition( relativePosition ); \endverbatim * * To have the legend positioned at a fixed point, measured from the QPainter's top left corner, * you could use the following code code: * \verbatim KChart::RelativePosition relativePosition; relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) ); relativePosition.setReferencePosition( Position::NorthWest ); relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft ); relativePosition.setHorizontalPadding( KChart::Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) ); relativePosition.setVerticalPadding( KChart::Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) ); m_legend->setFloatingPosition( relativePosition ); \endverbatim * Actually that's exactly the code KChart is using as default position for any floating legends, * so if you just say setPosition( KChart::Position::Floating ) without calling setFloatingPosition * your legend will be positioned at point 4/4. * * \sa setPosition, setAlignment */ void setFloatingPosition( const RelativePosition& relativePosition ); /** * Returns the position of a floating legend. * \sa setFloatingPosition */ const RelativePosition floatingPosition() const; void setOrientation( Qt::Orientation orientation ); Qt::Orientation orientation() const; void setSortOrder( Qt::SortOrder order ); Qt::SortOrder sortOrder() const; void setShowLines( bool legendShowLines ); bool showLines() const; + + /** + \brief Removes all legend texts that might have been set by setText. + + This resets the Legend to default behaviour: Texts are created automatically. + */ void resetTexts(); void setText( uint dataset, const QString& text ); QString text( uint dataset ) const; const QMap texts() const; /** * Sets a list of datasets that are to be hidden in the legend. * * By passing an empty list, you show all datasets. * All datasets are shown by default, which means * that hiddenDatasets() returns an empty list. */ void setHiddenDatasets( const QList hiddenDatasets ); const QList hiddenDatasets() const; void setDatasetHidden( uint dataset, bool hidden ); bool datasetIsHidden( uint dataset ) const; uint datasetCount() const; void setDefaultColors(); void setRainbowColors(); void setSubduedColors( bool ordered = false ); void setBrushesFromDiagram( KChart::AbstractDiagram* diagram ); /** * Note: there is no color() getter method, since setColor * just sets a QBrush with the respective color, so the * brush() getter method is sufficient. */ void setColor( uint dataset, const QColor& color ); void setBrush( uint dataset, const QBrush& brush ); QBrush brush( uint dataset ) const; const QMap brushes() const; void setPen( uint dataset, const QPen& pen ); QPen pen( uint dataset ) const; const QMap pens() const; /** * Note that any sizes specified via setMarkerAttributes are ignored, * unless you disable the automatic size calculation, by saying * setUseAutomaticMarkerSize( false ) */ void setMarkerAttributes( uint dataset, const MarkerAttributes& ); MarkerAttributes markerAttributes( uint dataset ) const; const QMap markerAttributes() const; /** * This option is on by default, it means that Marker sizes in the Legend * will be the same as the font height used for their respective label texts. * * Set this to false, if you want to specify the marker sizes via setMarkerAttributes * or if you want the Legend to use the same marker sizes as they are used in the Diagrams. */ void setUseAutomaticMarkerSize( bool useAutomaticMarkerSize ); bool useAutomaticMarkerSize() const; void setTextAttributes( const TextAttributes &a ); TextAttributes textAttributes() const; void setTitleText( const QString& text ); QString titleText() const; void setTitleTextAttributes( const TextAttributes &a ); TextAttributes titleTextAttributes() const; void setSpacing( uint space ); uint spacing() const; // called internally by KChart::Chart, when painting into a custom QPainter void forceRebuild() override; QSize minimumSizeHint() const override; QSize sizeHint() const override; bool hasHeightForWidth() const override; int heightForWidth( int width ) const override; void needSizeHint() override; void resizeLayout( const QSize& size ) override; Q_SIGNALS: void destroyedLegend( Legend* ); /** Emitted upon change of a property of the Legend or any of its components. */ void propertiesChanged(); private Q_SLOTS: void emitPositionChanged(); void resetDiagram( AbstractDiagram* ); void activateTheLayout(); void setNeedRebuild(); void buildLegend(); }; // End of class Legend } #endif // KCHARTLEGEND_H diff --git a/src/KChart/KChartTextLabelCache.cpp b/src/KChart/KChartTextLabelCache.cpp index 9b23bda..b98d63f 100644 --- a/src/KChart/KChartTextLabelCache.cpp +++ b/src/KChart/KChartTextLabelCache.cpp @@ -1,318 +1,291 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartTextLabelCache.h" #include #include #include #include #include #include #ifndef NDEBUG int HitCount = 0; int MissCount = 0; #define INC_HIT_COUNT { ++HitCount; } #define INC_MISS_COUNT { ++MissCount; } #define DUMP_CACHE_STATS \ if ( HitCount != 0 && MissCount != 0 ) { \ int total = HitCount + MissCount; \ qreal hitQuote = ( 1.0 * HitCount ) / total; \ qDebug() << "PrerenderedLabel dtor: hits/misses/total:" \ << HitCount << "/" << MissCount << "/" << total \ << "(" << 100 * hitQuote << "% hits)"; \ } #else #define INC_HIT_COUNT #define INC_MISS_COUNT #define DUMP_CACHE_STATS #endif PrerenderedElement::PrerenderedElement() : m_referencePoint( KChartEnums::PositionNorthWest ) { } void PrerenderedElement::setPosition( const QPointF& position ) { // this does not invalidate the element m_position = position; } const QPointF& PrerenderedElement::position() const { return m_position; } void PrerenderedElement::setReferencePoint( KChartEnums::PositionValue point ) { // this does not invalidate the element m_referencePoint = point; } KChartEnums::PositionValue PrerenderedElement::referencePoint() const { return m_referencePoint; } PrerenderedLabel::PrerenderedLabel() : PrerenderedElement() , m_dirty( true ) , m_font( qApp->font() ) , m_brush( Qt::black ) , m_pen( Qt::black ) // do not use anything invisible , m_angle( 0.0 ) { } PrerenderedLabel::~PrerenderedLabel() { DUMP_CACHE_STATS; } -/** - * Invalidates the preredendered data, forces re-rendering. - */ void PrerenderedLabel::invalidate() const { m_dirty = true; } -/** - * Sets the label's font to \a font. - */ void PrerenderedLabel::setFont( const QFont& font ) { m_font = font; invalidate(); } -/** - * @return the label's font. - */ const QFont& PrerenderedLabel::font() const { return m_font; } -/** - * Sets the label's text to \a text - */ void PrerenderedLabel::setText( const QString& text ) { m_text = text; invalidate(); } -/** - * @return the label's text - */ const QString& PrerenderedLabel::text() const { return m_text; } -/** - * Sets the label's brush to \a brush - */ void PrerenderedLabel::setBrush( const QBrush& brush ) { m_brush = brush; invalidate(); } -/** - * @return the label's brush - */ const QBrush& PrerenderedLabel::brush() const { return m_brush; } -/** - * Sets the angle of the label to \a angle degrees - */ void PrerenderedLabel::setAngle( qreal angle ) { m_angle = angle; invalidate(); } -/** - * @return the label's angle in degrees - */ qreal PrerenderedLabel::angle() const { return m_angle; } const QPixmap& PrerenderedLabel::pixmap() const { if ( m_dirty ) { INC_MISS_COUNT; paint(); } else { INC_HIT_COUNT; } return m_pixmap; } void PrerenderedLabel::paint() const { // FIXME find a better value using font metrics of text (this // requires finding the diameter of the circle formed by rotating // the bounding rect around the center): const int Width = 1000; const int Height = Width; QRectF boundingRect; const QColor FullTransparent( 255, 255, 255, 0 ); #ifdef Q_WS_X11 QImage pixmap( Width, Height, QImage::Format_ARGB32_Premultiplied ); qWarning() << "PrerenderedLabel::paint: using QImage for prerendered labels " << "to work around XRender/Qt4 bug."; #else QPixmap pixmap( Width, Height ); #endif // pixmap.fill( FullTransparent ); { static const QPointF Center ( 0.0, 0.0 ); QPointF textBottomRight; QPainter painter( &pixmap ); painter.setRenderHint(QPainter::TextAntialiasing, true ); painter.setRenderHint(QPainter::Antialiasing, true ); // QImage (X11 workaround) does not have fill(): painter.setPen( FullTransparent ); painter.setBrush( FullTransparent ); QPainter::CompositionMode mode = painter.compositionMode(); painter.setCompositionMode( QPainter::CompositionMode_Clear ); painter.drawRect( 0, 0, Width, Height ); painter.setCompositionMode( mode ); QMatrix matrix; matrix.translate( 0.5 * Width, 0.5 * Height ); matrix.rotate( m_angle ); painter.setWorldMatrix( matrix ); painter.setPen( m_pen ); painter.setBrush( m_brush ); painter.setFont( m_font ); QRectF container( -0.5 * Width, -0.5 * Height, Width, 0.5 * Height ); painter.drawText( container, Qt::AlignHCenter | Qt::AlignBottom, m_text, &boundingRect ); m_referenceBottomLeft = QPointF( boundingRect.bottomLeft().x(), 0.0 ); textBottomRight = QPointF( boundingRect.bottomRight().x(), 0.0 ); m_textAscendVector = boundingRect.topRight() - textBottomRight; m_textBaseLineVector = textBottomRight - m_referenceBottomLeft; // FIXME translate topright by char height boundingRect = matrix.mapRect( boundingRect ); m_referenceBottomLeft = matrix.map( m_referenceBottomLeft ) - boundingRect.topLeft(); textBottomRight = matrix.map( textBottomRight ) - boundingRect.topLeft(); m_textAscendVector = matrix.map( m_textAscendVector ) - matrix.map( Center ); m_textBaseLineVector = matrix.map( m_textBaseLineVector ) - matrix.map( Center ); } m_dirty = false; // now all the calculation vectors are valid QPixmap temp( static_cast( boundingRect.width() ), static_cast( boundingRect.height() ) ); { temp.fill( FullTransparent ); QPainter painter( &temp ); #ifdef Q_WS_X11 painter.drawImage( QPointF( 0.0, 0.0 ), pixmap, boundingRect ); #else painter.drawPixmap( QPointF( 0.0, 0.0 ), pixmap, boundingRect ); #endif // #define PRERENDEREDLABEL_DEBUG #ifdef PRERENDEREDLABEL_DEBUG painter.setPen( QPen( Qt::red, 2 ) ); painter.setBrush( Qt::red ); // paint markers for the reference points QList positions; positions << KChartEnums::PositionCenter << KChartEnums::PositionNorthWest << KChartEnums::PositionNorth << KChartEnums::PositionNorthEast << KChartEnums::PositionEast << KChartEnums::PositionSouthEast << KChartEnums::PositionSouth << KChartEnums::PositionSouthWest << KChartEnums::PositionWest; Q_FOREACH( KChartEnums::PositionValue position, positions ) { //krazy:exclude=foreach static const double Radius = 0.5; static const double Diameter = 2 * Radius; QPointF point ( referencePointLocation( position ) ); painter.drawEllipse( QRectF( point - QPointF( Radius, Radius ), QSizeF( Diameter, Diameter ) ) ); } #endif } m_pixmap = temp; } QPointF PrerenderedLabel::referencePointLocation() const { return referencePointLocation( referencePoint() ); } QPointF PrerenderedLabel::referencePointLocation( KChartEnums::PositionValue position ) const { if ( m_dirty ) { INC_MISS_COUNT; paint(); } else { INC_HIT_COUNT; } switch ( position ) { case KChartEnums::PositionCenter: return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + 0.5 * m_textAscendVector; case KChartEnums::PositionNorthWest: return m_referenceBottomLeft + m_textAscendVector; case KChartEnums::PositionNorth: return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + m_textAscendVector; case KChartEnums::PositionNorthEast: return m_referenceBottomLeft + m_textBaseLineVector + m_textAscendVector; case KChartEnums::PositionEast: return m_referenceBottomLeft + 0.5 * m_textAscendVector; case KChartEnums::PositionSouthEast: return m_referenceBottomLeft + m_textBaseLineVector; case KChartEnums::PositionSouth: return m_referenceBottomLeft + 0.5 * m_textBaseLineVector; case KChartEnums::PositionSouthWest: return m_referenceBottomLeft; case KChartEnums::PositionWest: return m_referenceBottomLeft + m_textBaseLineVector + 0.5 * m_textAscendVector; case KChartEnums::PositionUnknown: // intentional fall-through case KChartEnums::PositionFloating: // intentional fall-through return QPointF(); } return QPointF(); } diff --git a/src/KChart/KChartTextLabelCache.h b/src/KChart/KChartTextLabelCache.h index c05cbaf..8c9c6ec 100644 --- a/src/KChart/KChartTextLabelCache.h +++ b/src/KChart/KChartTextLabelCache.h @@ -1,145 +1,181 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTTEXTLABELCACHE_H #define KCHARTTEXTLABELCACHE_H #include #include #include #include #include "KChartEnums.h" /** * @brief base class for prerendered elements like labels, pixmaps, markers, etc. */ class PrerenderedElement { public: PrerenderedElement(); virtual ~PrerenderedElement() {} /** Returns the rendered element. If any of the properties have change, the element will be regenerated. */ virtual const QPixmap& pixmap() const = 0; /** Return the location of the reference point relatively to the pixmap's origin. */ virtual QPointF referencePointLocation( KChartEnums::PositionValue ) const = 0; /** Set the position of the element. */ void setPosition( const QPointF& position ); /** Get the position of the element. */ const QPointF& position() const; /** Set the reference point of the element. Every element has nine possible reference points (all compass directions, plus the center. */ void setReferencePoint( KChartEnums::PositionValue ); /** Get the reference point of the element. */ KChartEnums::PositionValue referencePoint() const; protected: /** invalidate() needs to be called if any of the properties that determine the visual appearance of the prerendered element change. It can be called for a const object, as objects may need to force recalculation of the pixmap. */ virtual void invalidate() const = 0; private: QPointF m_position; KChartEnums::PositionValue m_referencePoint; }; -/** +/** @brief PrerenderedLabel is an internal KChart class that simplifies creation and caching of cached text labels. - + It provides referenze points to anchor the text to other elements. Reference points use the positions defined in KChartEnums. Usage:
     qreal angle = 90.0;
     CachedLabel label;
     label.paint( font, tr("Label"), angle );
     
*/ // FIXME this is merely a prototype // FIXME caching could be done by a second layer that can be used to, // e.g., query for a prerendered element by id or name, or by changing // the pixmap() method to do lazy evaluation. class PrerenderedLabel : public PrerenderedElement { public: PrerenderedLabel(); ~PrerenderedLabel(); + + /** + * Sets the label's font to \a font. + */ void setFont( const QFont& font ); + + /** + * @return the label's font. + */ const QFont& font() const; + + /** + * Sets the label's text to \a text + */ void setText( const QString& text ); + + /** + * @return the label's text + */ const QString& text() const; + + /** + * Sets the label's brush to \a brush + */ void setBrush( const QBrush& brush ); + + /** + * @return the label's brush + */ const QBrush& brush() const; void setPen( const QPen& ); const QPen& pen() const; + + /** + * Sets the angle of the label to \a angle degrees + */ void setAngle( qreal angle ); + + /** + * @return the label's angle in degrees + */ qreal angle() const; // reimpl PrerenderedElement: const QPixmap& pixmap() const override; QPointF referencePointLocation( KChartEnums::PositionValue position ) const override; // overload: return location of referencePoint(): QPointF referencePointLocation() const; protected: + + /** + * Invalidates the preredendered data, forces re-rendering. + */ void invalidate() const override; private: /** Create a label with the given text and the given rotation angle. Needs to be const, otherwise the pixmap() method cannot update when needed. */ void paint() const; // store the settings (these are used for the painting): mutable bool m_dirty; QFont m_font; QString m_text; QBrush m_brush; QPen m_pen; qreal m_angle; // these are valid once the label has been rendered: mutable QPixmap m_pixmap; mutable QPointF m_referenceBottomLeft; mutable QPointF m_textBaseLineVector; mutable QPointF m_textAscendVector; }; #endif diff --git a/src/KChart/KChartWidget.cpp b/src/KChart/KChartWidget.cpp index 6bd11cd..c1e7d48 100644 --- a/src/KChart/KChartWidget.cpp +++ b/src/KChart/KChartWidget.cpp @@ -1,625 +1,551 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "KChartMath_p.h" #include #define d d_func() using namespace KChart; Widget::Private::Private( Widget * qq ) : q( qq ), layout( q ), m_model( q ), m_chart( q ), m_cartPlane( &m_chart ), m_polPlane( &m_chart ), usedDatasetWidth( 0 ) { KDAB_SET_OBJECT_NAME( layout ); KDAB_SET_OBJECT_NAME( m_model ); KDAB_SET_OBJECT_NAME( m_chart ); layout.addWidget( &m_chart ); } Widget::Private::~Private() {} -/** -* \class Widget KChartWidget.h -* \brief The KChart widget for usage without Interwiev. -* -* If you want to use KChart with Interview, use KChart::Chart instead. -*/ - -/** - * Constructor. Creates a new widget with all data initialized empty. - * - * \param parent the widget parent; passed on to QWidget - */ + Widget::Widget( QWidget* parent ) : QWidget(parent), _d( new Private( this ) ) { // as default we have a cartesian coordinate plane ... // ... and a line diagram setType( Line ); } -/** - * Destructor. - */ Widget::~Widget() { delete _d; _d = nullptr; } void Widget::init() { } void Widget::setDataset( int column, const QVector< qreal > & data, const QString& title ) { if ( ! checkDatasetWidth( 1 ) ) return; QStandardItemModel & model = d->m_model; justifyModelSize( data.size(), column + 1 ); for ( int i = 0; i < data.size(); ++i ) { const QModelIndex index = model.index( i, column ); model.setData( index, QVariant( data[i] ), Qt::DisplayRole ); } if ( ! title.isEmpty() ) model.setHeaderData( column, Qt::Horizontal, QVariant( title ) ); } void Widget::setDataset( int column, const QVector< QPair< qreal, qreal > > & data, const QString& title ) { if ( ! checkDatasetWidth( 2 )) return; QStandardItemModel & model = d->m_model; justifyModelSize( data.size(), (column + 1) * 2 ); for ( int i = 0; i < data.size(); ++i ) { QModelIndex index = model.index( i, column * 2 ); model.setData( index, QVariant( data[i].first ), Qt::DisplayRole ); index = model.index( i, column * 2 + 1 ); model.setData( index, QVariant( data[i].second ), Qt::DisplayRole ); } if ( ! title.isEmpty() ) { model.setHeaderData( column, Qt::Horizontal, QVariant( title ) ); } } void Widget::setDataCell( int row, int column, qreal data ) { if ( ! checkDatasetWidth( 1 ) ) return; QStandardItemModel & model = d->m_model; justifyModelSize( row + 1, column + 1 ); const QModelIndex index = model.index( row, column ); model.setData( index, QVariant( data ), Qt::DisplayRole ); } void Widget::setDataCell( int row, int column, QPair< qreal, qreal > data ) { if ( ! checkDatasetWidth( 2 )) return; QStandardItemModel & model = d->m_model; justifyModelSize( row + 1, (column + 1) * 2 ); QModelIndex index = model.index( row, column * 2 ); model.setData( index, QVariant( data.first ), Qt::DisplayRole ); index = model.index( row, column * 2 + 1 ); model.setData( index, QVariant( data.second ), Qt::DisplayRole ); } /* * Resets all data. */ void Widget::resetData() { d->m_model.clear(); d->usedDatasetWidth = 0; } -/** - * Sets all global leadings (borders). - */ void Widget::setGlobalLeading( int left, int top, int right, int bottom ) { d->m_chart.setGlobalLeading( left, top, right, bottom ); } -/** - * Sets the left leading (border). - */ void Widget::setGlobalLeadingLeft( int leading ) { d->m_chart.setGlobalLeadingLeft( leading ); } -/** - * Returns the left leading (border). - */ int Widget::globalLeadingLeft() const { return d->m_chart.globalLeadingLeft(); } -/** - * Sets the top leading (border). - */ void Widget::setGlobalLeadingTop( int leading ) { d->m_chart.setGlobalLeadingTop( leading ); } -/** - * Returns the top leading (border). - */ int Widget::globalLeadingTop() const { return d->m_chart.globalLeadingTop(); } -/** - * Sets the right leading (border). - */ void Widget::setGlobalLeadingRight( int leading ) { d->m_chart.setGlobalLeadingRight( leading ); } -/** - * Returns the right leading (border). - */ int Widget::globalLeadingRight() const { return d->m_chart.globalLeadingRight(); } -/** - * Sets the bottom leading (border). - */ void Widget::setGlobalLeadingBottom( int leading ) { d->m_chart.setGlobalLeadingBottom( leading ); } -/** - * Returns the bottom leading (border). - */ int Widget::globalLeadingBottom() const { return d->m_chart.globalLeadingBottom(); } -/** - * Returns the first of all headers. - */ KChart::HeaderFooter* Widget::firstHeaderFooter() { return d->m_chart.headerFooter(); } -/** - * Returns a list with all headers. - */ QList Widget::allHeadersFooters() { return d->m_chart.headerFooters(); } -/** - * Adds a new header/footer with the given text to the position. - */ void Widget::addHeaderFooter( const QString& text, HeaderFooter::HeaderFooterType type, Position position) { HeaderFooter* newHeader = new HeaderFooter( &d->m_chart ); newHeader->setType( type ); newHeader->setPosition( position ); newHeader->setText( text ); d->m_chart.addHeaderFooter( newHeader ); // we need this explicit call ! } -/** - * Adds an existing header / footer object. - */ void Widget::addHeaderFooter( HeaderFooter* header ) { header->setParent( &d->m_chart ); d->m_chart.addHeaderFooter( header ); // we need this explicit call ! } void Widget::replaceHeaderFooter( HeaderFooter* header, HeaderFooter* oldHeader ) { header->setParent( &d->m_chart ); d->m_chart.replaceHeaderFooter( header, oldHeader ); } void Widget::takeHeaderFooter( HeaderFooter* header ) { d->m_chart.takeHeaderFooter( header ); } -/** - * Returns the first of all legends. - */ KChart::Legend* Widget::legend() { return d->m_chart.legend(); } -/** - * Returns a list with all legends. - */ QList Widget::allLegends() { return d->m_chart.legends(); } -/** - * Adds an empty legend on the given position. - */ void Widget::addLegend( Position position ) { Legend* legend = new Legend( diagram(), &d->m_chart ); legend->setPosition( position ); d->m_chart.addLegend( legend ); } -/** - * Adds a new, already existing, legend. - */ void Widget::addLegend( Legend* legend ) { legend->setDiagram( diagram() ); legend->setParent( &d->m_chart ); d->m_chart.addLegend( legend ); } void Widget::replaceLegend( Legend* legend, Legend* oldLegend ) { legend->setDiagram( diagram() ); legend->setParent( &d->m_chart ); d->m_chart.replaceLegend( legend, oldLegend ); } void Widget::takeLegend( Legend* legend ) { d->m_chart.takeLegend( legend ); } AbstractDiagram* Widget::diagram() { if ( coordinatePlane() == nullptr ) qDebug() << "diagram(): coordinatePlane() was NULL"; return coordinatePlane()->diagram(); } BarDiagram* Widget::barDiagram() { return dynamic_cast( diagram() ); } LineDiagram* Widget::lineDiagram() { return dynamic_cast( diagram() ); } Plotter* Widget::plotter() { return dynamic_cast( diagram() ); } PieDiagram* Widget::pieDiagram() { return dynamic_cast( diagram() ); } RingDiagram* Widget::ringDiagram() { return dynamic_cast( diagram() ); } PolarDiagram* Widget::polarDiagram() { return dynamic_cast( diagram() ); } AbstractCoordinatePlane* Widget::coordinatePlane() { return d->m_chart.coordinatePlane(); } static bool isCartesian( KChart::Widget::ChartType type ) { return (type == KChart::Widget::Bar) || (type == KChart::Widget::Line); } static bool isPolar( KChart::Widget::ChartType type ) { return (type == KChart::Widget::Pie) || (type == KChart::Widget::Ring) || (type == KChart::Widget::Polar); } void Widget::setType( ChartType chartType, SubType chartSubType ) { AbstractDiagram* diag = nullptr; const ChartType oldType = type(); if ( chartType != oldType ) { if ( chartType != NoType ) { if ( isCartesian( chartType ) && ! isCartesian( oldType ) ) { if ( coordinatePlane() == &d->m_polPlane ) { d->m_chart.takeCoordinatePlane( &d->m_polPlane ); d->m_chart.addCoordinatePlane( &d->m_cartPlane ); } else { d->m_chart.replaceCoordinatePlane( &d->m_cartPlane ); } } else if ( isPolar( chartType ) && ! isPolar( oldType ) ) { if ( coordinatePlane() == &d->m_cartPlane ) { d->m_chart.takeCoordinatePlane( &d->m_cartPlane ); d->m_chart.addCoordinatePlane( &d->m_polPlane ); } else { d->m_chart.replaceCoordinatePlane( &d->m_polPlane ); } } } switch ( chartType ) { case Bar: diag = new BarDiagram( &d->m_chart, &d->m_cartPlane ); break; case Line: diag = new LineDiagram( &d->m_chart, &d->m_cartPlane ); break; case Plot: diag = new Plotter( &d->m_chart, &d->m_cartPlane ); break; case Pie: diag = new PieDiagram( &d->m_chart, &d->m_polPlane ); break; case Polar: diag = new PolarDiagram( &d->m_chart, &d->m_polPlane ); break; case Ring: diag = new RingDiagram( &d->m_chart, &d->m_polPlane ); break; case NoType: break; } if ( diag != nullptr ) { if ( isCartesian( oldType ) && isCartesian( chartType ) ) { AbstractCartesianDiagram *oldDiag = qobject_cast( coordinatePlane()->diagram() ); AbstractCartesianDiagram *newDiag = qobject_cast( diag ); Q_FOREACH( CartesianAxis* axis, oldDiag->axes() ) { oldDiag->takeAxis( axis ); newDiag->addAxis ( axis ); } } Q_FOREACH( Legend* l, d->m_chart.legends() ) { l->setDiagram( diag ); } diag->setModel( &d->m_model ); coordinatePlane()->replaceDiagram( diag ); //checkDatasetWidth( d->usedDatasetWidth ); } //coordinatePlane()->setGridNeedsRecalculate(); } if ( chartType != NoType ) { if ( chartType != oldType || chartSubType != subType() ) setSubType( chartSubType ); d->m_chart.resize( size() ); // triggering immediate update } } template< class DiagramType, class Subtype > void setSubtype( AbstractDiagram *_dia, Subtype st) { if ( DiagramType *dia = qobject_cast< DiagramType * >( _dia ) ) { dia->setType( st ); } } void Widget::setSubType( SubType subType ) { // ### at least PieDiagram, PolarDiagram and RingDiagram are unhandled here AbstractDiagram *dia = diagram(); switch ( subType ) { case Normal: setSubtype< BarDiagram >( dia, BarDiagram::Normal ); setSubtype< LineDiagram >( dia, LineDiagram::Normal ); setSubtype< Plotter >( dia, Plotter::Normal ); break; case Stacked: setSubtype< BarDiagram >( dia, BarDiagram::Stacked ); setSubtype< LineDiagram >( dia, LineDiagram::Stacked ); // setSubtype< Plotter >( dia, Plotter::Stacked ); break; case Percent: setSubtype< BarDiagram >( dia, BarDiagram::Percent ); setSubtype< LineDiagram >( dia, LineDiagram::Percent ); setSubtype< Plotter >( dia, Plotter::Percent ); break; case Rows: setSubtype< BarDiagram >( dia, BarDiagram::Rows ); break; default: Q_ASSERT_X ( false, "Widget::setSubType", "Sub-type not supported!" ); break; } } -/** - * Returns the type of the chart. - */ Widget::ChartType Widget::type() const { // PENDING(christoph) save the type out-of-band: AbstractDiagram * const dia = const_cast( this )->diagram(); if ( qobject_cast< BarDiagram* >( dia ) ) return Bar; else if ( qobject_cast< LineDiagram* >( dia ) ) return Line; else if ( qobject_cast< Plotter* >( dia ) ) return Plot; else if ( qobject_cast< PieDiagram* >( dia ) ) return Pie; else if ( qobject_cast< PolarDiagram* >( dia ) ) return Polar; else if ( qobject_cast< RingDiagram* >( dia ) ) return Ring; else return NoType; } Widget::SubType Widget::subType() const { // PENDING(christoph) save the type out-of-band: Widget::SubType retVal = Normal; AbstractDiagram * const dia = const_cast( this )->diagram(); BarDiagram* barDia = qobject_cast< BarDiagram* >( dia ); LineDiagram* lineDia = qobject_cast< LineDiagram* >( dia ); Plotter* plotterDia = qobject_cast< Plotter* >( dia ); //FIXME(khz): Add the impl for these chart types - or remove them from here: // PieDiagram* pieDia = qobject_cast< PieDiagram* >( diagram() ); // PolarDiagram* polarDia = qobject_cast< PolarDiagram* >( diagram() ); // RingDiagram* ringDia = qobject_cast< RingDiagram* >( diagram() ); #define TEST_SUB_TYPE(DIAGRAM, INTERNALSUBTYPE, SUBTYPE) \ { \ if ( DIAGRAM && DIAGRAM->type() == INTERNALSUBTYPE ) \ retVal = SUBTYPE; \ } const Widget::ChartType mainType = type(); switch ( mainType ) { case Bar: TEST_SUB_TYPE( barDia, BarDiagram::Normal, Normal ); TEST_SUB_TYPE( barDia, BarDiagram::Stacked, Stacked ); TEST_SUB_TYPE( barDia, BarDiagram::Percent, Percent ); TEST_SUB_TYPE( barDia, BarDiagram::Rows, Rows ); break; case Line: TEST_SUB_TYPE( lineDia, LineDiagram::Normal, Normal ); TEST_SUB_TYPE( lineDia, LineDiagram::Stacked, Stacked ); TEST_SUB_TYPE( lineDia, LineDiagram::Percent, Percent ); break; case Plot: TEST_SUB_TYPE( plotterDia, Plotter::Normal, Normal ); TEST_SUB_TYPE( plotterDia, Plotter::Percent, Percent ); break; case Pie: // no impl. yet break; case Polar: // no impl. yet break; case Ring: // no impl. yet break; default: Q_ASSERT_X ( false, "Widget::subType", "Chart type not supported!" ); break; } return retVal; } -/** - * Checks whether the given width matches with the one used until now. - */ bool Widget::checkDatasetWidth( int width ) { if ( width == diagram()->datasetDimension() ) { d->usedDatasetWidth = width; return true; } qDebug() << "The current diagram type doesn't support this data dimension."; return false; /* if ( d->usedDatasetWidth == width || d->usedDatasetWidth == 0 ) { d->usedDatasetWidth = width; diagram()->setDatasetDimension( width ); return true; } qDebug() << "It's impossible to mix up the different setDataset() methods on the same widget."; return false;*/ } -/** - * Justifies the model, so that the given rows and columns fit into it. - */ void Widget::justifyModelSize( int rows, int columns ) { QAbstractItemModel & model = d->m_model; const int currentRows = model.rowCount(); const int currentCols = model.columnCount(); if ( currentCols < columns ) if ( ! model.insertColumns( currentCols, columns - currentCols )) qDebug() << "justifyModelSize: could not increase model size."; if ( currentRows < rows ) if ( ! model.insertRows( currentRows, rows - currentRows )) qDebug() << "justifyModelSize: could not increase model size."; Q_ASSERT( model.rowCount() >= rows ); Q_ASSERT( model.columnCount() >= columns ); } diff --git a/src/KChart/KChartWidget.h b/src/KChart/KChartWidget.h index fe3f255..6472050 100644 --- a/src/KChart/KChartWidget.h +++ b/src/KChart/KChartWidget.h @@ -1,239 +1,239 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef __KCHARTWIDGET_H__ #define __KCHARTWIDGET_H__ #include "KChartGlobal.h" #include #include "KChartEnums.h" #include "KChartHeaderFooter.h" QT_BEGIN_NAMESPACE template class QVector; template struct QPair; QT_END_NAMESPACE namespace KChart { // some forward declarations class AbstractDiagram; class Chart; class AbstractCoordinatePlane; class TableModel; class BarDiagram; class LineDiagram; class Plotter; class PieDiagram; class RingDiagram; class PolarDiagram; class Legend; class Position; /** - * \class Widget KChartWidget.h - * \brief The KChart widget for usage without Model/View. - * - * If you want to use KChart with Model/View, use KChart::Chart instead. - */ - class KCHART_EXPORT Widget : public QWidget + * \class Widget KChartWidget.h + * \brief The KChart widget for usage without Interwiev. + * + * If you want to use KChart with Interview, use KChart::Chart instead. + */ + class KCHART_EXPORT Widget : public QWidget { Q_OBJECT Q_DISABLE_COPY( Widget ) KCHART_DECLARE_PRIVATE_BASE_POLYMORPHIC_QWIDGET( Widget ) public: /** * Standard Qt-style Constructor * * Creates a new widget with all data initialized empty. * * \param parent the widget parent; passed on to QWidget */ - explicit Widget( QWidget* parent = nullptr ); + explicit Widget( QWidget* parent = nullptr ); /** Destructor. */ - ~Widget(); + ~Widget(); /** Sets the data in the given column using a QVector of qreal for the Y values. */ void setDataset( int column, const QVector< qreal > & data, const QString& title = QString() ); /** Sets the data in the given column using a QVector of QPairs * of qreal for the (X, Y) values. */ void setDataset( int column, const QVector< QPair< qreal, qreal > > & data, const QString& title = QString() ); /** Sets the Y value data for a given cell. */ void setDataCell( int row, int column, qreal data ); /** Sets the data for a given column using an (X, Y) QPair of qreals. */ void setDataCell( int row, int column, QPair< qreal, qreal > data ); /** Resets all data. */ void resetData(); public Q_SLOTS: /** Sets all global leadings (borders). */ void setGlobalLeading( int left, int top, int right, int bottom ); /** Sets the left leading (border). */ - void setGlobalLeadingLeft( int leading ); + void setGlobalLeadingLeft( int leading ); /** Sets the top leading (border). */ - void setGlobalLeadingTop( int leading ); + void setGlobalLeadingTop( int leading ); /** Sets the right leading (border). */ - void setGlobalLeadingRight( int leading ); + void setGlobalLeadingRight( int leading ); /** Sets the bottom leading (border). */ - void setGlobalLeadingBottom( int leading ); + void setGlobalLeadingBottom( int leading ); public: /** Returns the left leading (border). */ int globalLeadingLeft() const; /** Returns the top leading (border). */ int globalLeadingTop() const; /** Returns the right leading (border). */ int globalLeadingRight() const; /** Returns the bottom leading (border). */ int globalLeadingBottom() const; /** Returns the first of all headers. */ HeaderFooter* firstHeaderFooter(); /** Returns a list with all headers. */ QList allHeadersFooters(); /** Adds a new header/footer with the given text to the position. */ void addHeaderFooter( const QString& text, HeaderFooter::HeaderFooterType type, Position position ); /** * Adds the existing header / footer object \a header. * \sa replaceHeaderFooter, takeHeaderFooter */ void addHeaderFooter( HeaderFooter* header ); /** * Replaces the old header (or footer, resp.), or appends the * new header or footer, it there is none yet. * * @param header The header or footer to be used instead of the old one. * This parameter must not be zero, or the method will do nothing. * * @param oldHeader The header or footer to be removed by the new one. This * header or footer will be deleted automatically. If the parameter is omitted, * the very first header or footer will be replaced. In case, there was no * header and no footer yet, the new header or footer will just be added. * * \note If you want to re-use the old header or footer, call takeHeaderFooter and * addHeaderFooter, instead of using replaceHeaderFooter. * * \sa addHeaderFooter, takeHeaderFooter */ void replaceHeaderFooter( HeaderFooter* header, HeaderFooter* oldHeader = nullptr ); /** Remove the header (or footer, resp.) from the widget, * without deleting it. * The chart no longer owns the header or footer, so it is * the caller's responsibility to delete the header or footer. * * \sa addHeaderFooter, replaceHeaderFooter */ void takeHeaderFooter( HeaderFooter* header ); /** Returns the first of all legends. */ Legend* legend(); /** Returns a list with all legends. */ QList allLegends(); /** Adds an empty legend on the given position. */ void addLegend( Position position ); /** Adds a new, already existing, legend. */ void addLegend (Legend* legend ); void replaceLegend( Legend* legend, Legend* oldLegend = nullptr ); void takeLegend( Legend* legend ); /** Returns a pointer to the current diagram. */ AbstractDiagram* diagram(); /** If the current diagram is a BarDiagram, it is returnd; otherwise 0 is returned. * This function provides type-safe casting. */ BarDiagram* barDiagram(); /** If the current diagram is a LineDiagram, it is returnd; otherwise 0 is returned. * This function provides type-safe casting. */ LineDiagram* lineDiagram(); /** If the current diagram is a LineDiagram, it is returnd; otherwise 0 is returned. * This function provides type-safe casting. * * \note Do not use lineDiagram for multi-dimensional diagrams, but use plotter instead * * \sa plotter */ Plotter* plotter(); /** If the current diagram is a Plotter, it is returnd; otherwise 0 is returned. * This function provides type-safe casting. */ PieDiagram* pieDiagram(); /** If the current diagram is a RingDiagram, it is returnd; otherwise 0 is returned. * This function provides type-safe casting. */ RingDiagram* ringDiagram(); /** If the current diagram is a PolarDiagram, it is returnd; otherwise 0 is returned. * This function provides type-safe casting. */ PolarDiagram* polarDiagram(); /** Returns a pointer to the current coordinate plane. */ AbstractCoordinatePlane* coordinatePlane(); enum ChartType { NoType, Bar, Line, Plot, Pie, Ring, Polar }; /** Returns the type of the chart. */ ChartType type() const; /** Sub type values, matching the values defines for the respective Diagram classes. */ enum SubType { Normal, Stacked, Percent, Rows }; /** Returns the sub-type of the chart. */ SubType subType() const; public Q_SLOTS: /** Sets the type of the chart. */ void setType( ChartType chartType, SubType subType=Normal ); /** \brief Sets the type of the chart without changing the main type. * * Make sure to use a sub-type that matches the main type, * so e.g. setting sub-type Rows makes sense for Bar charts only, * and it will be ignored for all other chart types. * * \sa KChart::BarDiagram::BarType, KChart::LineDiagram::LineType * \sa KChart::PieDiagram::PieType, KChart::RingDiagram::RingType * \sa KChart::PolarDiagram::PolarType */ void setSubType( SubType subType ); private: /** Justifies the model, so that the given rows and columns fit into it. */ void justifyModelSize( int rows, int columns ); /** Checks whether the given width matches with the one used until now. */ - bool checkDatasetWidth( int width ); + bool checkDatasetWidth( int width ); }; } #endif // KChartWidget_H diff --git a/src/KChart/Polar/KChartPieDiagram.cpp b/src/KChart/Polar/KChartPieDiagram.cpp index a7db17d..0cb2fe2 100644 --- a/src/KChart/Polar/KChartPieDiagram.cpp +++ b/src/KChart/Polar/KChartPieDiagram.cpp @@ -1,966 +1,901 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartPieDiagram.h" #include "KChartPieDiagram_p.h" #include "KChartPaintContext.h" #include "KChartPieAttributes.h" #include "KChartPolarCoordinatePlane_p.h" #include "KChartThreeDPieAttributes.h" #include "KChartPainterSaver_p.h" #include "KChartMath_p.h" #include #include #include using namespace KChart; PieDiagram::Private::Private() : labelDecorations( PieDiagram::NoDecoration ), isCollisionAvoidanceEnabled( false ) { } PieDiagram::Private::~Private() {} #define d d_func() PieDiagram::PieDiagram( QWidget* parent, PolarCoordinatePlane* plane ) : AbstractPieDiagram( new Private(), parent, plane ) { init(); } PieDiagram::~PieDiagram() { } void PieDiagram::init() { } -/** - * Creates an exact copy of this diagram. - */ PieDiagram * PieDiagram::clone() const { return new PieDiagram( new Private( *d ) ); } void PieDiagram::setLabelDecorations( LabelDecorations decorations ) { d->labelDecorations = decorations; } PieDiagram::LabelDecorations PieDiagram::labelDecorations() const { return d->labelDecorations; } void PieDiagram::setLabelCollisionAvoidanceEnabled( bool enabled ) { d->isCollisionAvoidanceEnabled = enabled; } bool PieDiagram::isLabelCollisionAvoidanceEnabled() const { return d->isCollisionAvoidanceEnabled; } const QPair PieDiagram::calculateDataBoundaries () const { if ( !checkInvariants( true ) || model()->rowCount() < 1 ) return QPair( QPointF( 0, 0 ), QPointF( 0, 0 ) ); const PieAttributes attrs( pieAttributes() ); QPointF bottomLeft( QPointF( 0, 0 ) ); QPointF topRight; // If we explode, we need extra space for the slice that has the largest explosion distance. if ( attrs.explode() ) { const int colCount = columnCount(); qreal maxExplode = 0.0; for ( int j = 0; j < colCount; ++j ) { const PieAttributes columnAttrs( pieAttributes( model()->index( 0, j, rootIndex() ) ) ); // checked maxExplode = qMax( maxExplode, columnAttrs.explodeFactor() ); } topRight = QPointF( 1.0 + maxExplode, 1.0 + maxExplode ); } else { topRight = QPointF( 1.0, 1.0 ); } return QPair ( bottomLeft, topRight ); } void PieDiagram::paintEvent( QPaintEvent* ) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void PieDiagram::resizeEvent( QResizeEvent* ) { } void PieDiagram::resize( const QSizeF& size ) { AbstractPieDiagram::resize(size); } void PieDiagram::paint( PaintContext* ctx ) { // Painting is a two stage process // In the first stage we figure out how much space is needed // for text labels. // In the second stage, we make use of that information and // perform the actual painting. placeLabels( ctx ); paintInternal( ctx ); } void PieDiagram::calcSliceAngles() { // determine slice positions and sizes const qreal sum = valueTotals(); const qreal sectorsPerValue = 360.0 / sum; const PolarCoordinatePlane* plane = polarCoordinatePlane(); qreal currentValue = plane ? plane->startPosition() : 0.0; const int colCount = columnCount(); d->startAngles.resize( colCount ); d->angleLens.resize( colCount ); bool atLeastOneValue = false; // guard against completely empty tables for ( int iColumn = 0; iColumn < colCount; ++iColumn ) { bool isOk; const qreal cellValue = qAbs( model()->data( model()->index( 0, iColumn, rootIndex() ) ) // checked .toReal( &isOk ) ); // toReal() returns 0.0 if there was no value or a non-numeric value atLeastOneValue = atLeastOneValue || isOk; d->startAngles[ iColumn ] = currentValue; d->angleLens[ iColumn ] = cellValue * sectorsPerValue; currentValue = d->startAngles[ iColumn ] + d->angleLens[ iColumn ]; } // If there was no value at all, this is the sign for other code to bail out if ( !atLeastOneValue ) { d->startAngles.clear(); d->angleLens.clear(); } } void PieDiagram::calcPieSize( const QRectF &contentsRect ) { d->size = qMin( contentsRect.width(), contentsRect.height() ); // if any slice explodes, the whole pie needs additional space so we make the basic size smaller qreal maxExplode = 0.0; const int colCount = columnCount(); for ( int j = 0; j < colCount; ++j ) { const PieAttributes columnAttrs( pieAttributes( model()->index( 0, j, rootIndex() ) ) ); // checked maxExplode = qMax( maxExplode, columnAttrs.explodeFactor() ); } d->size /= ( 1.0 + 1.0 * maxExplode ); if ( d->size < 0.0 ) { d->size = 0; } } // this is the rect of the top surface of the pie, i.e. excluding the "3D" rim effect. QRectF PieDiagram::twoDPieRect( const QRectF &contentsRect, const ThreeDPieAttributes& threeDAttrs ) const { QRectF pieRect; if ( !threeDAttrs.isEnabled() ) { qreal x = ( contentsRect.width() - d->size ) / 2.0; qreal y = ( contentsRect.height() - d->size ) / 2.0; pieRect = QRectF( contentsRect.left() + x, contentsRect.top() + y, d->size, d->size ); } else { // threeD: width is the maximum possible width; height is 1/2 of that qreal sizeFor3DEffect = 0.0; qreal x = ( contentsRect.width() - d->size ) / 2.0; qreal height = d->size; // make sure that the height plus the threeDheight is not more than the // available size if ( threeDAttrs.depth() >= 0.0 ) { // positive pie height: absolute value sizeFor3DEffect = threeDAttrs.depth(); height = d->size - sizeFor3DEffect; } else { // negative pie height: relative value sizeFor3DEffect = - threeDAttrs.depth() / 100.0 * height; height = d->size - sizeFor3DEffect; } qreal y = ( contentsRect.height() - height - sizeFor3DEffect ) / 2.0; pieRect = QRectF( contentsRect.left() + x, contentsRect.top() + y, d->size, height ); } return pieRect; } void PieDiagram::placeLabels( PaintContext* paintContext ) { if ( !checkInvariants(true) || model()->rowCount() < 1 ) { return; } if ( paintContext->rectangle().isEmpty() || valueTotals() == 0.0 ) { return; } const ThreeDPieAttributes threeDAttrs( threeDPieAttributes() ); const int colCount = columnCount(); d->reverseMapper.clear(); // on first call, this sets up the internals of the ReverseMapper. calcSliceAngles(); if ( d->startAngles.isEmpty() ) { return; } calcPieSize( paintContext->rectangle() ); // keep resizing the pie until the labels and the pie fit into paintContext->rectangle() bool tryAgain = true; while ( tryAgain ) { tryAgain = false; QRectF pieRect = twoDPieRect( paintContext->rectangle(), threeDAttrs ); d->forgetAlreadyPaintedDataValues(); d->labelPaintCache.clear(); for ( int slice = 0; slice < colCount; slice++ ) { if ( d->angleLens[ slice ] != 0.0 ) { const QRectF explodedPieRect = explodedDrawPosition( pieRect, slice ); addSliceLabel( &d->labelPaintCache, explodedPieRect, slice ); } } QRectF textBoundingRect; d->paintDataValueTextsAndMarkers( paintContext, d->labelPaintCache, false, true, &textBoundingRect ); if ( d->isCollisionAvoidanceEnabled ) { shuffleLabels( &textBoundingRect ); } if ( !textBoundingRect.isEmpty() && d->size > 0.0 ) { const QRectF &clipRect = paintContext->rectangle(); // see by how many pixels the text is clipped on each side qreal right = qMax( qreal( 0.0 ), textBoundingRect.right() - clipRect.right() ); qreal left = qMax( qreal( 0.0 ), clipRect.left() - textBoundingRect.left() ); // attention here - y coordinates in Qt are inverted compared to the convention in maths qreal top = qMax( qreal( 0.0 ), clipRect.top() - textBoundingRect.top() ); qreal bottom = qMax( qreal( 0.0 ), textBoundingRect.bottom() - clipRect.bottom() ); qreal maxOverhang = qMax( qMax( right, left ), qMax( top, bottom ) ); if ( maxOverhang > 0.0 ) { // subtract 2x as much because every side only gets half of the total diameter reduction // and we have to make up for the overhang on one particular side. d->size -= qMin( d->size, maxOverhang * 2.0 ); tryAgain = true; } } } } static int wraparound( int i, int size ) { while ( i < 0 ) { i += size; } while ( i >= size ) { i -= size; } return i; } //#define SHUFFLE_DEBUG void PieDiagram::shuffleLabels( QRectF* textBoundingRect ) { // things that could be improved here: // - use a variable number (chosen using angle information) of neighbors to check // - try harder to arrange the labels to look nice // ideas: // - leave labels that don't collide alone (only if they their offset is zero) // - use a graphics view for collision detection LabelPaintCache& lpc = d->labelPaintCache; const int n = lpc.paintReplay.size(); bool modified = false; qreal direction = 5.0; QVector< qreal > offsets; offsets.fill( 0.0, n ); for ( bool lastRoundModified = true; lastRoundModified; ) { lastRoundModified = false; for ( int i = 0; i < n; i++ ) { const int neighborsToCheck = qMax( 10, lpc.paintReplay.size() - 1 ); const int minComp = wraparound( i - neighborsToCheck / 2, n ); const int maxComp = wraparound( i + ( neighborsToCheck + 1 ) / 2, n ); QPainterPath& path = lpc.paintReplay[ i ].labelArea; for ( int j = minComp; j != maxComp; j = wraparound( j + 1, n ) ) { if ( i == j ) { continue; } QPainterPath& otherPath = lpc.paintReplay[ j ].labelArea; while ( ( offsets[ i ] + direction > 0 ) && otherPath.intersects( path ) ) { #ifdef SHUFFLE_DEBUG qDebug() << "collision involving" << j << "and" << i << " -- n =" << n; TextAttributes ta = lpc.paintReplay[ i ].attrs.textAttributes(); ta.setPen( QPen( Qt::white ) ); lpc.paintReplay[ i ].attrs.setTextAttributes( ta ); #endif uint slice = lpc.paintReplay[ i ].index.column(); qreal angle = DEGTORAD( d->startAngles[ slice ] + d->angleLens[ slice ] / 2.0 ); qreal dx = cos( angle ) * direction; qreal dy = -sin( angle ) * direction; offsets[ i ] += direction; path.translate( dx, dy ); lastRoundModified = true; } } } direction *= -1.07; // this can "overshoot", but avoids getting trapped in local minimums modified = modified || lastRoundModified; } if ( modified ) { for ( int i = 0; i < lpc.paintReplay.size(); i++ ) { *textBoundingRect |= lpc.paintReplay[ i ].labelArea.boundingRect(); } } } static QPolygonF polygonFromPainterPath( const QPainterPath &pp ) { QPolygonF ret; for ( int i = 0; i < pp.elementCount(); i++ ) { const QPainterPath::Element& el = pp.elementAt( i ); Q_ASSERT( el.type == QPainterPath::MoveToElement || el.type == QPainterPath::LineToElement ); ret.append( el ); } return ret; } // you can call it "normalizedProjectionLength" if you like static qreal normProjection( const QLineF &l1, const QLineF &l2 ) { const qreal dotProduct = l1.dx() * l2.dx() + l1.dy() * l2.dy(); return qAbs( dotProduct / ( l1.length() * l2.length() ) ); } static QLineF labelAttachmentLine( const QPointF ¢er, const QPointF &start, const QPainterPath &label ) { Q_ASSERT ( label.elementCount() == 5 ); // start is assumed to lie on the outer rim of the slice(!), making it possible to derive the // radius of the pie const qreal pieRadius = QLineF( center, start ).length(); // don't draw a line at all when the label is connected to its slice due to at least one of its // corners falling inside the slice. for ( int i = 0; i < 4; i++ ) { // point 4 is just a duplicate of point 0 if ( QLineF( label.elementAt( i ), center ).length() < pieRadius ) { return QLineF(); } } // find the closest edge in the polygon, and its two neighbors QPointF closeCorners[3]; { QPointF closest = QPointF( 1000000, 1000000 ); int closestIndex = 0; // better misbehave than crash for ( int i = 0; i < 4; i++ ) { // point 4 is just a duplicate of point 0 QPointF p = label.elementAt( i ); if ( QLineF( p, center ).length() < QLineF( closest, center ).length() ) { closest = p; closestIndex = i; } } closeCorners[ 0 ] = label.elementAt( wraparound( closestIndex - 1, 4 ) ); closeCorners[ 1 ] = closest; closeCorners[ 2 ] = label.elementAt( wraparound( closestIndex + 1, 4 ) ); } QLineF edge1 = QLineF( closeCorners[ 0 ], closeCorners[ 1 ] ); QLineF edge2 = QLineF( closeCorners[ 1 ], closeCorners[ 2 ] ); QLineF connection1 = QLineF( ( closeCorners[ 0 ] + closeCorners[ 1 ] ) / 2.0, center ); QLineF connection2 = QLineF( ( closeCorners[ 1 ] + closeCorners[ 2 ] ) / 2.0, center ); QLineF ret; // prefer the connecting line meeting its edge at a more perpendicular angle if ( normProjection( edge1, connection1 ) < normProjection( edge2, connection2 ) ) { ret = connection1; } else { ret = connection2; } // This tends to look a bit better than not doing it *shrug* ret.setP2( ( start + center ) / 2.0 ); // make the line end at the rim of the slice (not 100% accurate because the line is not precisely radial) qreal p1Radius = QLineF( ret.p1(), center ).length(); ret.setLength( p1Radius - pieRadius ); return ret; } void PieDiagram::paintInternal( PaintContext* paintContext ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants( true ) || model()->rowCount() < 1 ) { return; } if ( d->startAngles.isEmpty() || paintContext->rectangle().isEmpty() || valueTotals() == 0.0 ) { return; } const ThreeDPieAttributes threeDAttrs( threeDPieAttributes() ); const int colCount = columnCount(); // Paint from back to front ("painter's algorithm") - first draw the backmost slice, // then the slices on the left and right from back to front, then the frontmost one. QRectF pieRect = twoDPieRect( paintContext->rectangle(), threeDAttrs ); const int backmostSlice = findSliceAt( 90, colCount ); const int frontmostSlice = findSliceAt( 270, colCount ); int currentLeftSlice = backmostSlice; int currentRightSlice = backmostSlice; drawSlice( paintContext->painter(), pieRect, backmostSlice ); if ( backmostSlice == frontmostSlice ) { const int rightmostSlice = findSliceAt( 0, colCount ); const int leftmostSlice = findSliceAt( 180, colCount ); if ( backmostSlice == leftmostSlice ) { currentLeftSlice = findLeftSlice( currentLeftSlice, colCount ); } if ( backmostSlice == rightmostSlice ) { currentRightSlice = findRightSlice( currentRightSlice, colCount ); } } while ( currentLeftSlice != frontmostSlice ) { if ( currentLeftSlice != backmostSlice ) { drawSlice( paintContext->painter(), pieRect, currentLeftSlice ); } currentLeftSlice = findLeftSlice( currentLeftSlice, colCount ); } while ( currentRightSlice != frontmostSlice ) { if ( currentRightSlice != backmostSlice ) { drawSlice( paintContext->painter(), pieRect, currentRightSlice ); } currentRightSlice = findRightSlice( currentRightSlice, colCount ); } // if the backmost slice is not the frontmost slice, we draw the frontmost one last if ( backmostSlice != frontmostSlice || ! threeDPieAttributes().isEnabled() ) { drawSlice( paintContext->painter(), pieRect, frontmostSlice ); } d->paintDataValueTextsAndMarkers( paintContext, d->labelPaintCache, false, false ); // it's safer to do this at the beginning of placeLabels, but we can save some memory here. d->forgetAlreadyPaintedDataValues(); // ### maybe move this into AbstractDiagram, also make ReverseMapper deal better with multiple polygons const QPointF center = paintContext->rectangle().center(); const PainterSaver painterSaver( paintContext->painter() ); paintContext->painter()->setBrush( Qt::NoBrush ); Q_FOREACH( const LabelPaintInfo &pi, d->labelPaintCache.paintReplay ) { // we expect the PainterPath to be a rectangle if ( pi.labelArea.elementCount() != 5 ) { continue; } paintContext->painter()->setPen( pen( pi.index ) ); if ( d->labelDecorations & LineFromSliceDecoration ) { paintContext->painter()->drawLine( labelAttachmentLine( center, pi.markerPos, pi.labelArea ) ); } if ( d->labelDecorations & FrameDecoration ) { paintContext->painter()->drawPath( pi.labelArea ); } d->reverseMapper.addPolygon( pi.index.row(), pi.index.column(), polygonFromPainterPath( pi.labelArea ) ); } d->labelPaintCache.clear(); d->startAngles.clear(); d->angleLens.clear(); } #if defined ( Q_OS_WIN) #define trunc(x) ((int)(x)) #endif QRectF PieDiagram::explodedDrawPosition( const QRectF& drawPosition, uint slice ) const { const QModelIndex index( model()->index( 0, slice, rootIndex() ) ); // checked const PieAttributes attrs( pieAttributes( index ) ); QRectF adjustedDrawPosition = drawPosition; if ( attrs.explode() ) { qreal startAngle = d->startAngles[ slice ]; qreal angleLen = d->angleLens[ slice ]; qreal explodeAngle = ( DEGTORAD( startAngle + angleLen / 2.0 ) ); qreal explodeDistance = attrs.explodeFactor() * d->size / 2.0; adjustedDrawPosition.translate( explodeDistance * cos( explodeAngle ), explodeDistance * - sin( explodeAngle ) ); } return adjustedDrawPosition; } -/** - Internal method that draws one of the slices in a pie chart. - - \param painter the QPainter to draw in - \param dataset the dataset to draw the pie for - \param slice the slice to draw - \param threeDPieHeight the height of the three dimensional effect - */ void PieDiagram::drawSlice( QPainter* painter, const QRectF& drawPosition, uint slice) { // Is there anything to draw at all? if ( d->angleLens[ slice ] == 0.0 ) { return; } const QRectF adjustedDrawPosition = explodedDrawPosition( drawPosition, slice ); draw3DEffect( painter, adjustedDrawPosition, slice ); drawSliceSurface( painter, adjustedDrawPosition, slice ); } -/** - Internal method that draws the surface of one of the slices in a pie chart. - - \param painter the QPainter to draw in - \param dataset the dataset to draw the slice for - \param slice the slice to draw - */ void PieDiagram::drawSliceSurface( QPainter* painter, const QRectF& drawPosition, uint slice ) { // Is there anything to draw at all? const qreal angleLen = d->angleLens[ slice ]; const qreal startAngle = d->startAngles[ slice ]; const QModelIndex index( model()->index( 0, slice, rootIndex() ) ); // checked const PieAttributes attrs( pieAttributes( index ) ); const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) ); painter->setRenderHint ( QPainter::Antialiasing ); QBrush br = brush( index ); if ( threeDAttrs.isEnabled() ) { br = threeDAttrs.threeDBrush( br, drawPosition ); } painter->setBrush( br ); QPen pen = this->pen( index ); if ( threeDAttrs.isEnabled() ) { pen.setColor( Qt::black ); } painter->setPen( pen ); if ( angleLen == 360 ) { // full circle, avoid nasty line in the middle painter->drawEllipse( drawPosition ); //Add polygon to Reverse mapper for showing tool tips. QPolygonF poly( drawPosition ); d->reverseMapper.addPolygon( index.row(), index.column(), poly ); } else { // draw the top of this piece // Start with getting the points for the arc. const int arcPoints = static_cast(trunc( angleLen / granularity() )); QPolygonF poly( arcPoints + 2 ); qreal degree = 0.0; int iPoint = 0; bool perfectMatch = false; while ( degree <= angleLen ) { poly[ iPoint ] = pointOnEllipse( drawPosition, startAngle + degree ); //qDebug() << degree << angleLen << poly[ iPoint ]; perfectMatch = ( degree == angleLen ); degree += granularity(); ++iPoint; } // if necessary add one more point to fill the last small gap if ( !perfectMatch ) { poly[ iPoint ] = pointOnEllipse( drawPosition, startAngle + angleLen ); // add the center point of the piece poly.append( drawPosition.center() ); } else { poly[ iPoint ] = drawPosition.center(); } //find the value and paint it //fix value position d->reverseMapper.addPolygon( index.row(), index.column(), poly ); painter->drawPolygon( poly ); } } // calculate the position points for the label and pass them to addLabel() void PieDiagram::addSliceLabel( LabelPaintCache* lpc, const QRectF& drawPosition, uint slice ) { const qreal angleLen = d->angleLens[ slice ]; const qreal startAngle = d->startAngles[ slice ]; const QModelIndex index( model()->index( 0, slice, rootIndex() ) ); // checked const qreal sum = valueTotals(); // Position points are calculated relative to the slice. // They are calculated as if the slice was 'standing' on its tip and the rim was up, // so North is the middle (also highest part) of the rim and South is the tip of the slice. const QPointF south = drawPosition.center(); const QPointF southEast = south; const QPointF southWest = south; const QPointF north = pointOnEllipse( drawPosition, startAngle + angleLen / 2.0 ); const QPointF northEast = pointOnEllipse( drawPosition, startAngle ); const QPointF northWest = pointOnEllipse( drawPosition, startAngle + angleLen ); QPointF center = ( south + north ) / 2.0; const QPointF east = ( south + northEast ) / 2.0; const QPointF west = ( south + northWest ) / 2.0; PositionPoints points( center, northWest, north, northEast, east, southEast, south, southWest, west ); qreal topAngle = startAngle - 90; if ( topAngle < 0.0 ) { topAngle += 360.0; } points.setDegrees( KChartEnums::PositionEast, topAngle ); points.setDegrees( KChartEnums::PositionNorthEast, topAngle ); points.setDegrees( KChartEnums::PositionWest, topAngle + angleLen ); points.setDegrees( KChartEnums::PositionNorthWest, topAngle + angleLen ); points.setDegrees( KChartEnums::PositionCenter, topAngle + angleLen / 2.0 ); points.setDegrees( KChartEnums::PositionNorth, topAngle + angleLen / 2.0 ); qreal favoriteTextAngle = 0.0; if ( autoRotateLabels() ) { favoriteTextAngle = - ( startAngle + angleLen / 2 ) + 90.0; while ( favoriteTextAngle <= 0.0 ) { favoriteTextAngle += 360.0; } // flip the label when upside down if ( favoriteTextAngle > 90.0 && favoriteTextAngle < 270.0 ) { favoriteTextAngle = favoriteTextAngle - 180.0; } // negative angles can have special meaning in addLabel; otherwise they work fine if ( favoriteTextAngle <= 0.0 ) { favoriteTextAngle += 360.0; } } d->addLabel( lpc, index, nullptr, points, Position::Center, Position::Center, angleLen * sum / 360, favoriteTextAngle ); } static bool doSpansOverlap( qreal s1Start, qreal s1End, qreal s2Start, qreal s2End ) { if ( s1Start < s2Start ) { return s1End >= s2Start; } else { return s1Start <= s2End; } } static bool doArcsOverlap( qreal a1Start, qreal a1End, qreal a2Start, qreal a2End ) { Q_ASSERT( a1Start >= 0 && a1Start <= 360 && a1End >= 0 && a1End <= 360 && a2Start >= 0 && a2Start <= 360 && a2End >= 0 && a2End <= 360 ); // all of this could probably be done better... if ( a1End < a1Start ) { a1End += 360; } if ( a2End < a2Start ) { a2End += 360; } if ( doSpansOverlap( a1Start, a1End, a2Start, a2End ) ) { return true; } if ( a1Start > a2Start ) { return doSpansOverlap( a1Start - 360.0, a1End - 360.0, a2Start, a2End ); } else { return doSpansOverlap( a1Start + 360.0, a1End + 360.0, a2Start, a2End ); } } -/** - Internal method that draws the shadow creating the 3D effect of a pie - - \param painter the QPainter to draw in - \param drawPosition the position to draw at - \param slice the slice to draw the shadow for - */ void PieDiagram::draw3DEffect( QPainter* painter, const QRectF& drawPosition, uint slice ) { const QModelIndex index( model()->index( 0, slice, rootIndex() ) ); // checked const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) ); if ( ! threeDAttrs.isEnabled() ) { return; } // NOTE: We cannot optimize away drawing some of the effects (even // when not exploding), because some of the pies might be left out // in future versions which would make some of the normally hidden // pies visible. Complex hidden-line algorithms would be much more // expensive than just drawing for nothing. // No need to save the brush, will be changed on return from this // method anyway. const QBrush brush = this->brush( model()->index( 0, slice, rootIndex() ) ); // checked if ( threeDAttrs.useShadowColors() ) { painter->setBrush( QBrush( brush.color().darker() ) ); } else { painter->setBrush( brush ); } qreal startAngle = d->startAngles[ slice ]; qreal endAngle = startAngle + d->angleLens[ slice ]; // Normalize angles while ( startAngle >= 360 ) startAngle -= 360; while ( endAngle >= 360 ) endAngle -= 360; Q_ASSERT( startAngle >= 0 && startAngle <= 360 ); Q_ASSERT( endAngle >= 0 && endAngle <= 360 ); // positive pie height: absolute value // negative pie height: relative value const int depth = threeDAttrs.depth() >= 0.0 ? threeDAttrs.depth() : -threeDAttrs.depth() / 100.0 * drawPosition.height(); if ( startAngle == endAngle || startAngle == endAngle - 360 ) { // full circle draw3dOuterRim( painter, drawPosition, depth, 180, 360 ); } else { if ( doArcsOverlap( startAngle, endAngle, 180, 360 ) ) { draw3dOuterRim( painter, drawPosition, depth, startAngle, endAngle ); } if ( startAngle >= 270 || startAngle <= 90 ) { draw3dCutSurface( painter, drawPosition, depth, startAngle ); } if ( endAngle >= 90 && endAngle <= 270 ) { draw3dCutSurface( painter, drawPosition, depth, endAngle ); } } } -/** - Internal method that draws the cut surface of a slice (think of a real pie cut into slices) - in 3D mode, for surfaces that are facing the observer. - - \param painter the QPainter to draw in - \param rect the position to draw at - \param threeDHeight the height of the shadow - \param angle the angle of the segment - */ void PieDiagram::draw3dCutSurface( QPainter* painter, const QRectF& rect, qreal threeDHeight, qreal angle ) { QPolygonF poly( 4 ); const QPointF center = rect.center(); const QPointF circlePoint = pointOnEllipse( rect, angle ); poly[0] = center; poly[1] = circlePoint; poly[2] = QPointF( circlePoint.x(), circlePoint.y() + threeDHeight ); poly[3] = QPointF( center.x(), center.y() + threeDHeight ); // TODO: add polygon to ReverseMapper painter->drawPolygon( poly ); } -/** - Internal method that draws the outer rim of a slice when the rim is facing the observer. - - \param painter the QPainter to draw in - \param rect the position to draw at - \param threeDHeight the height of the shadow - \param startAngle the starting angle of the segment - \param endAngle the ending angle of the segment - */ void PieDiagram::draw3dOuterRim( QPainter* painter, const QRectF& rect, qreal threeDHeight, qreal startAngle, qreal endAngle ) { // Start with getting the points for the inner arc. if ( endAngle < startAngle ) { endAngle += 360; } startAngle = qMax( startAngle, qreal( 180.0 ) ); endAngle = qMin( endAngle, qreal( 360.0 ) ); int numHalfPoints = trunc( ( endAngle - startAngle ) / granularity() ) + 1; if ( numHalfPoints < 2 ) { return; } QPolygonF poly( numHalfPoints ); qreal degree = endAngle; int iPoint = 0; bool perfectMatch = false; while ( degree >= startAngle ) { poly[ numHalfPoints - iPoint - 1 ] = pointOnEllipse( rect, degree ); perfectMatch = (degree == startAngle); degree -= granularity(); ++iPoint; } // if necessary add one more point to fill the last small gap if ( !perfectMatch ) { poly.prepend( pointOnEllipse( rect, startAngle ) ); ++numHalfPoints; } poly.resize( numHalfPoints * 2 ); // Now copy these arcs again into the final array, but in the // opposite direction and moved down by the 3D height. for ( int i = numHalfPoints - 1; i >= 0; --i ) { QPointF pointOnFirstArc( poly[ i ] ); pointOnFirstArc.setY( pointOnFirstArc.y() + threeDHeight ); poly[ numHalfPoints * 2 - i - 1 ] = pointOnFirstArc; } // TODO: Add polygon to ReverseMapper painter->drawPolygon( poly ); } -/** - Internal method that finds the slice that is located at the position specified by \c angle. - - \param angle the angle at which to search for a slice - \return the number of the slice found - */ uint PieDiagram::findSliceAt( qreal angle, int colCount ) { for ( int i = 0; i < colCount; ++i ) { qreal endseg = d->startAngles[ i ] + d->angleLens[ i ]; if ( d->startAngles[ i ] <= angle && endseg >= angle ) { return i; } } // If we have not found it, try wrap around // but only if the current searched angle is < 360 degree if ( angle < 360 ) return findSliceAt( angle + 360, colCount ); // otherwise - what ever went wrong - we return 0 return 0; } -/** - Internal method that finds the slice that is located to the left of \c slice. - - \param slice the slice to start the search from - \return the number of the pie to the left of \c pie - */ uint PieDiagram::findLeftSlice( uint slice, int colCount ) { if ( slice == 0 ) { if ( colCount > 1 ) { return colCount - 1; } else { return 0; } } else { return slice - 1; } } -/** - Internal method that finds the slice that is located to the right of \c slice. - - \param slice the slice to start the search from - \return the number of the slice to the right of \c slice - */ uint PieDiagram::findRightSlice( uint slice, int colCount ) { int rightSlice = slice + 1; if ( rightSlice == colCount ) { rightSlice = 0; } return rightSlice; } -/** - * Auxiliary method returning a point to a given boundary - * rectangle of the enclosed ellipse and an angle. - */ QPointF PieDiagram::pointOnEllipse( const QRectF& boundingBox, qreal angle ) { qreal angleRad = DEGTORAD( angle ); qreal cosAngle = cos( angleRad ); qreal sinAngle = -sin( angleRad ); qreal posX = cosAngle * boundingBox.width() / 2.0; qreal posY = sinAngle * boundingBox.height() / 2.0; return QPointF( posX + boundingBox.center().x(), posY + boundingBox.center().y() ); } /*virtual*/ qreal PieDiagram::valueTotals() const { if ( !model() ) return 0; const int colCount = columnCount(); qreal total = 0.0; // non-empty models need a row with data Q_ASSERT( colCount == 0 || model()->rowCount() >= 1 ); for ( int j = 0; j < colCount; ++j ) { total += qAbs(model()->data( model()->index( 0, j, rootIndex() ) ).toReal()); // checked } return total; } /*virtual*/ qreal PieDiagram::numberOfValuesPerDataset() const { return model() ? model()->columnCount( rootIndex() ) : 0.0; } /*virtual*/ qreal PieDiagram::numberOfGridRings() const { return 1; } diff --git a/src/KChart/Polar/KChartPieDiagram.h b/src/KChart/Polar/KChartPieDiagram.h index e4b1105..cf7dae3 100644 --- a/src/KChart/Polar/KChartPieDiagram.h +++ b/src/KChart/Polar/KChartPieDiagram.h @@ -1,123 +1,198 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTPIEDIAGRAM_H #define KCHARTPIEDIAGRAM_H #include "KChartAbstractPieDiagram.h" namespace KChart { class LabelPaintCache; /** * @brief PieDiagram defines a common pie diagram */ class KCHART_EXPORT PieDiagram : public AbstractPieDiagram { Q_OBJECT Q_DISABLE_COPY( PieDiagram ) KCHART_DECLARE_DERIVED_DIAGRAM( PieDiagram, PolarCoordinatePlane ) public: explicit PieDiagram( QWidget* parent = nullptr, PolarCoordinatePlane* plane = nullptr ); virtual ~PieDiagram(); protected: // Implement AbstractDiagram /** \reimpl */ void paint( PaintContext* paintContext ) override; public: /** * Describes which decorations are painted around data labels. */ enum LabelDecoration { NoDecoration = 0, ///< No decoration FrameDecoration = 1, ///< A rectangular frame is painted around the label text LineFromSliceDecoration = 2 ///< A line is drawn from the pie slice to its label }; Q_DECLARE_FLAGS( LabelDecorations, LabelDecoration ) /// Set the decorations to be painted around data labels according to @p decorations. void setLabelDecorations( LabelDecorations decorations ); /// Return the decorations to be painted around data labels. LabelDecorations labelDecorations() const; /// If @p enabled is set to true, labels that would overlap will be shuffled to avoid overlap. /// \note Collision avoidance may allow labels to be closer than AbstractDiagram with /// allowOverlappingDataValueTexts() == false, so you should usually also call /// setAllowOverlappingDataValueTexts( true ) if you enable this feature. void setLabelCollisionAvoidanceEnabled( bool enabled ); /// Return whether overlapping labels will be moved to until they don't overlap anymore. bool isLabelCollisionAvoidanceEnabled() const; /** \reimpl */ void resize ( const QSizeF& area ) override; // Implement AbstractPolarDiagram /** \reimpl */ qreal valueTotals () const override; /** \reimpl */ qreal numberOfValuesPerDataset() const override; /** \reimpl */ qreal numberOfGridRings() const override; - virtual PieDiagram * clone() const; + + /** + * Creates an exact copy of this diagram. + */ + virtual PieDiagram * clone() const; protected: /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent( QPaintEvent* ) override; void resizeEvent( QResizeEvent* ) override; private: // ### move to private class? void placeLabels( PaintContext* paintContext ); // Solve problems with label overlap by changing label positions inside d->labelPaintCache. void shuffleLabels( QRectF* textBoundingRect ); void paintInternal( PaintContext* paintContext ); + + /** + Internal method that draws one of the slices in a pie chart. + + \param painter the QPainter to draw in + \param dataset the dataset to draw the pie for + \param slice the slice to draw + \param threeDPieHeight the height of the three dimensional effect + */ void drawSlice( QPainter* painter, const QRectF& drawPosition, uint slice ); + + /** + Internal method that draws the surface of one of the slices in a pie chart. + + \param painter the QPainter to draw in + \param dataset the dataset to draw the slice for + \param slice the slice to draw + */ void drawSliceSurface( QPainter* painter, const QRectF& drawPosition, uint slice ); void addSliceLabel( LabelPaintCache* lpc, const QRectF& drawPosition, uint slice ); + + /** + Internal method that draws the shadow creating the 3D effect of a pie + + \param painter the QPainter to draw in + \param drawPosition the position to draw at + \param slice the slice to draw the shadow for + */ void draw3DEffect( QPainter* painter, const QRectF& drawPosition, uint slice ); + + /** + Internal method that draws the cut surface of a slice (think of a real pie cut into slices) + in 3D mode, for surfaces that are facing the observer. + + \param painter the QPainter to draw in + \param rect the position to draw at + \param threeDHeight the height of the shadow + \param angle the angle of the segment + */ void draw3dCutSurface( QPainter* painter, const QRectF& rect, qreal threeDHeight, qreal angle ); + + /** + Internal method that draws the outer rim of a slice when the rim is facing the observer. + + \param painter the QPainter to draw in + \param rect the position to draw at + \param threeDHeight the height of the shadow + \param startAngle the starting angle of the segment + \param endAngle the ending angle of the segment + */ void draw3dOuterRim( QPainter* painter, const QRectF& rect, qreal threeDHeight, qreal startAngle, qreal endAngle ); void calcSliceAngles(); void calcPieSize( const QRectF &contentsRect ); QRectF twoDPieRect( const QRectF &contentsRect, const ThreeDPieAttributes& threeDAttrs ) const; QRectF explodedDrawPosition( const QRectF& drawPosition, uint slice ) const; + + /** + Internal method that finds the slice that is located at the position specified by \c angle. + + \param angle the angle at which to search for a slice + \return the number of the slice found + */ uint findSliceAt( qreal angle, int columnCount ); + + /** + Internal method that finds the slice that is located to the left of \c slice. + + \param slice the slice to start the search from + \return the number of the pie to the left of \c pie + */ uint findLeftSlice( uint slice, int columnCount ); + + /** + Internal method that finds the slice that is located to the right of \c slice. + + \param slice the slice to start the search from + \return the number of the slice to the right of \c slice + */ uint findRightSlice( uint slice, int columnCount ); + + /** + * Auxiliary method returning a point to a given boundary + * rectangle of the enclosed ellipse and an angle. + */ QPointF pointOnEllipse( const QRectF& boundingBox, qreal angle ); }; // End of class KChartPieDiagram Q_DECLARE_OPERATORS_FOR_FLAGS( PieDiagram::LabelDecorations ) } #endif // KCHARTPIEDIAGRAM_H diff --git a/src/KChart/Polar/KChartPolarDiagram.cpp b/src/KChart/Polar/KChartPolarDiagram.cpp index 00cf49b..93c8835 100644 --- a/src/KChart/Polar/KChartPolarDiagram.cpp +++ b/src/KChart/Polar/KChartPolarDiagram.cpp @@ -1,306 +1,303 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartPolarDiagram.h" #include "KChartPolarDiagram_p.h" #include "KChartPaintContext.h" #include "KChartPainterSaver_p.h" #include "KChartMath_p.h" #include using namespace KChart; PolarDiagram::Private::Private() : rotateCircularLabels( false ), closeDatasets( false ) { } PolarDiagram::Private::~Private() {} #define d d_func() PolarDiagram::PolarDiagram( QWidget* parent, PolarCoordinatePlane* plane ) : AbstractPolarDiagram( new Private( ), parent, plane ) { //init(); } PolarDiagram::~PolarDiagram() { } void PolarDiagram::init() { setShowDelimitersAtPosition( Position::Unknown, false ); setShowDelimitersAtPosition( Position::Center, false ); setShowDelimitersAtPosition( Position::NorthWest, false ); setShowDelimitersAtPosition( Position::North, true ); setShowDelimitersAtPosition( Position::NorthEast, false ); setShowDelimitersAtPosition( Position::West, false ); setShowDelimitersAtPosition( Position::East, false ); setShowDelimitersAtPosition( Position::SouthWest, false ); setShowDelimitersAtPosition( Position::South, true ); setShowDelimitersAtPosition( Position::SouthEast, false ); setShowDelimitersAtPosition( Position::Floating, false ); setShowLabelsAtPosition( Position::Unknown, false ); setShowLabelsAtPosition( Position::Center, false ); setShowLabelsAtPosition( Position::NorthWest, false ); setShowLabelsAtPosition( Position::North, true ); setShowLabelsAtPosition( Position::NorthEast, false ); setShowLabelsAtPosition( Position::West, false ); setShowLabelsAtPosition( Position::East, false ); setShowLabelsAtPosition( Position::SouthWest, false ); setShowLabelsAtPosition( Position::South, true ); setShowLabelsAtPosition( Position::SouthEast, false ); setShowLabelsAtPosition( Position::Floating, false ); } -/** - * Creates an exact copy of this diagram. - */ PolarDiagram * PolarDiagram::clone() const { PolarDiagram* newDiagram = new PolarDiagram( new Private( *d ) ); // This needs to be copied after the fact newDiagram->d->showDelimitersAtPosition = d->showDelimitersAtPosition; newDiagram->d->showLabelsAtPosition = d->showLabelsAtPosition; newDiagram->d->rotateCircularLabels = d->rotateCircularLabels; newDiagram->d->closeDatasets = d->closeDatasets; return newDiagram; } const QPair PolarDiagram::calculateDataBoundaries () const { if ( !checkInvariants(true) ) return QPair( QPointF( 0, 0 ), QPointF( 0, 0 ) ); const int rowCount = model()->rowCount(rootIndex()); const int colCount = model()->columnCount(rootIndex()); qreal xMin = 0.0; qreal xMax = colCount; qreal yMin = 0, yMax = 0; for ( int iCol=0; iColdata( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked yMax = qMax( yMax, value ); yMin = qMin( yMin, value ); } } QPointF bottomLeft ( QPointF( xMin, yMin ) ); QPointF topRight ( QPointF( xMax, yMax ) ); return QPair ( bottomLeft, topRight ); } void PolarDiagram::paintEvent ( QPaintEvent*) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void PolarDiagram::resizeEvent ( QResizeEvent*) { } void PolarDiagram::paintPolarMarkers( PaintContext* ctx, const QPolygonF& polygon ) { Q_UNUSED(ctx); Q_UNUSED(polygon); // obsolete, since we are using real markers now! } void PolarDiagram::paint( PaintContext* ctx ) { qreal dummy1, dummy2; paint( ctx, true, dummy1, dummy2 ); paint( ctx, false, dummy1, dummy2 ); } void PolarDiagram::paint( PaintContext* ctx, bool calculateListAndReturnScale, qreal& newZoomX, qreal& newZoomY ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants(true) ) return; d->reverseMapper.clear(); const int rowCount = model()->rowCount( rootIndex() ); const int colCount = model()->columnCount( rootIndex() ); if ( calculateListAndReturnScale ) { // Check if all of the data value texts / data comments fit into the available space... d->labelPaintCache.clear(); for ( int iCol = 0; iCol < colCount; ++iCol ) { for ( int iRow=0; iRow < rowCount; ++iRow ) { QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked const qreal value = model()->data( index ).toReal(); QPointF point = coordinatePlane()->translate( QPointF( value, iRow ) ) + ctx->rectangle().topLeft(); //qDebug() << point; d->addLabel( &d->labelPaintCache, index, nullptr, PositionPoints( point ), Position::Center, Position::Center, value ); } } newZoomX = coordinatePlane()->zoomFactorX(); newZoomY = coordinatePlane()->zoomFactorY(); if ( d->labelPaintCache.paintReplay.count() ) { // ...and zoom out if necessary const qreal oldZoomX = newZoomX; const qreal oldZoomY = newZoomY; QRectF txtRectF; d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF ); const QRect txtRect = txtRectF.toRect(); const QRect curRect = coordinatePlane()->geometry(); const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right() - txtRect.right() ); const qreal gapY = qMin( txtRect.top() - curRect.top(), curRect.bottom() - txtRect.bottom() ); if ( gapX < 0.0 ) { newZoomX = oldZoomX * ( 1.0 + ( gapX - 1.0 ) / curRect.width() ); } if ( gapY < 0.0 ) { newZoomY = oldZoomY * ( 1.0 + ( gapY - 1.0 ) / curRect.height() ); } } } else { // Paint the data sets for ( int iCol = 0; iCol < colCount; ++iCol ) { //TODO(khz): As of yet PolarDiagram can not show per-segment line attributes // but it draws every polyline in one go - using one color. // This needs to be enhanced to allow for cell-specific settings // in the same way as LineDiagram does it. QBrush brush = d->datasetAttrs( iCol, KChart::DatasetBrushRole ).value(); QPolygonF polygon; for ( int iRow = 0; iRow < rowCount; ++iRow ) { QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked const qreal value = model()->data( index ).toReal(); QPointF point = coordinatePlane()->translate( QPointF( value, iRow ) ) + ctx->rectangle().topLeft(); polygon.append( point ); //qDebug() << point; } if ( closeDatasets() && !polygon.isEmpty() ) { // close the circle by connecting the last data point to the first polygon.append( polygon.first() ); } PainterSaver painterSaver( ctx->painter() ); ctx->painter()->setRenderHint ( QPainter::Antialiasing ); ctx->painter()->setBrush( brush ); QPen p = d->datasetAttrs( iCol, KChart::DatasetPenRole ).value< QPen >(); if ( p.style() != Qt::NoPen ) { ctx->painter()->setPen( PrintingParameters::scalePen( p ) ); ctx->painter()->drawPolyline( polygon ); } } d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true ); } } void PolarDiagram::resize ( const QSizeF& size ) { AbstractPolarDiagram::resize(size); } /*virtual*/ qreal PolarDiagram::valueTotals () const { return model()->rowCount(rootIndex()); } /*virtual*/ qreal PolarDiagram::numberOfValuesPerDataset() const { return model() ? model()->rowCount(rootIndex()) : 0.0; } /*virtual*/ qreal PolarDiagram::numberOfGridRings() const { return 5; // FIXME } void PolarDiagram::setZeroDegreePosition( int degrees ) { Q_UNUSED( degrees ); qWarning() << "Deprecated PolarDiagram::setZeroDegreePosition() called, setting ignored."; } int PolarDiagram::zeroDegreePosition() const { qWarning() << "Deprecated PolarDiagram::zeroDegreePosition() called."; return 0; } void PolarDiagram::setRotateCircularLabels( bool rotateCircularLabels ) { d->rotateCircularLabels = rotateCircularLabels; } bool PolarDiagram::rotateCircularLabels() const { return d->rotateCircularLabels; } void PolarDiagram::setCloseDatasets( bool closeDatasets ) { d->closeDatasets = closeDatasets; } bool PolarDiagram::closeDatasets() const { return d->closeDatasets; } void PolarDiagram::setShowDelimitersAtPosition( Position position, bool showDelimiters ) { d->showDelimitersAtPosition[position.value()] = showDelimiters; } void PolarDiagram::setShowLabelsAtPosition( Position position, bool showLabels ) { d->showLabelsAtPosition[position.value()] = showLabels; } bool PolarDiagram::showDelimitersAtPosition( Position position ) const { return d->showDelimitersAtPosition[position.value()]; } bool PolarDiagram::showLabelsAtPosition( Position position ) const { return d->showLabelsAtPosition[position.value()]; } diff --git a/src/KChart/Polar/KChartPolarDiagram.h b/src/KChart/Polar/KChartPolarDiagram.h index cdc9e61..47cdeca 100644 --- a/src/KChart/Polar/KChartPolarDiagram.h +++ b/src/KChart/Polar/KChartPolarDiagram.h @@ -1,106 +1,110 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTPOLARDIAGRAM_H #define KCHARTPOLARDIAGRAM_H #include "KChartPosition.h" #include "KChartAbstractPolarDiagram.h" QT_BEGIN_NAMESPACE class QPolygonF; QT_END_NAMESPACE namespace KChart { /** * @brief PolarDiagram defines a common polar diagram */ class KCHART_EXPORT PolarDiagram : public AbstractPolarDiagram { Q_OBJECT Q_DISABLE_COPY( PolarDiagram ) KCHART_DECLARE_DERIVED_DIAGRAM( PolarDiagram, PolarCoordinatePlane ) public: explicit PolarDiagram( QWidget* parent = nullptr, PolarCoordinatePlane* plane = nullptr ); virtual ~PolarDiagram(); protected: // Implement AbstractDiagram /** \reimpl */ void paint ( PaintContext* paintContext ) override; public: /** \reimpl */ void resize ( const QSizeF& area ) override; // Implement AbstractPolarDiagram /** \reimpl */ qreal valueTotals () const override; /** \reimpl */ qreal numberOfValuesPerDataset() const override; /** \reimpl */ qreal numberOfGridRings() const override; - virtual PolarDiagram * clone() const; + + /** + * Creates an exact copy of this diagram. + */ + virtual PolarDiagram * clone() const; /** \deprecated Use PolarCoordinatePlane::setStartPosition( qreal degrees ) instead. */ void setZeroDegreePosition( int degrees ); /** \deprecated Use qreal PolarCoordinatePlane::startPosition instead. */ int zeroDegreePosition() const; void setRotateCircularLabels( bool rotateCircularLabels ); bool rotateCircularLabels() const; /** Close each of the data series by connecting the last point to its * respective start point */ void setCloseDatasets( bool closeDatasets ); bool closeDatasets() const; void setShowDelimitersAtPosition( Position position, bool showDelimiters ); void setShowLabelsAtPosition( Position position, bool showLabels ); bool showDelimitersAtPosition( Position position ) const; bool showLabelsAtPosition( Position position ) const; virtual void paint ( PaintContext* paintContext, bool calculateListAndReturnScale, qreal& newZoomX, qreal& newZoomY ); // KChart 3: references -> pointers protected: /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent ( QPaintEvent* ) override; void resizeEvent ( QResizeEvent* ) override; virtual void paintPolarMarkers( PaintContext* ctx, const QPolygonF& polygon ); }; // End of class PolarDiagram } #endif // KCHARTPOLARDIAGRAM_H diff --git a/src/KChart/Polar/KChartRadarDiagram.cpp b/src/KChart/Polar/KChartRadarDiagram.cpp index 5f17c48..d03dc24 100644 --- a/src/KChart/Polar/KChartRadarDiagram.cpp +++ b/src/KChart/Polar/KChartRadarDiagram.cpp @@ -1,335 +1,332 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartRadarDiagram.h" #include "KChartRadarDiagram_p.h" #include "KChartPaintContext.h" #include "KChartPainterSaver_p.h" #include "KChartMath_p.h" #include using namespace KChart; RadarDiagram::Private::Private() : closeDatasets( false ), reverseData( false ), fillAlpha( 0.0 ) { } RadarDiagram::Private::~Private() {} #define d d_func() RadarDiagram::RadarDiagram( QWidget* parent, RadarCoordinatePlane* plane ) : AbstractPolarDiagram( new Private( ), parent, plane ) { //init(); } RadarDiagram::~RadarDiagram() { } void RadarDiagram::init() { } -/** - * Creates an exact copy of this diagram. - */ RadarDiagram * RadarDiagram::clone() const { RadarDiagram* newDiagram = new RadarDiagram( new Private( *d ) ); // This needs to be copied after the fact newDiagram->d->closeDatasets = d->closeDatasets; return newDiagram; } const QPair RadarDiagram::calculateDataBoundaries () const { if ( !checkInvariants(true) ) return QPair( QPointF( 0, 0 ), QPointF( 0, 0 ) ); const int rowCount = model()->rowCount(rootIndex()); const int colCount = model()->columnCount(rootIndex()); qreal xMin = 0.0; qreal xMax = colCount; qreal yMin = 0, yMax = 0; for ( int iCol=0; iColdata( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked yMax = qMax( yMax, value ); yMin = qMin( yMin, value ); } } QPointF bottomLeft ( QPointF( xMin, yMin ) ); QPointF topRight ( QPointF( xMax, yMax ) ); return QPair ( bottomLeft, topRight ); } void RadarDiagram::paintEvent ( QPaintEvent*) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void RadarDiagram::paint( PaintContext* ctx ) { qreal dummy1, dummy2; paint( ctx, true, dummy1, dummy2 ); paint( ctx, false, dummy1, dummy2 ); } static qreal fitFontSizeToGeometry( const QString& text, const QFont& font, const QRectF& geometry, const TextAttributes& ta ) { QFont f = font; const qreal origResult = f.pointSizeF(); qreal result = origResult; const QSizeF mySize = geometry.size(); if ( mySize.isNull() ) return result; const QString t = text; QFontMetrics fm( f ); while ( true ) { const QSizeF textSize = rotatedRect( fm.boundingRect( t ), ta.rotation() ).normalized().size(); if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() ) return result; result -= 0.5; if ( result <= 0.0 ) return origResult; f.setPointSizeF( result ); fm = QFontMetrics( f ); } } static QPointF scaleToRealPosition( const QPointF& origin, const QRectF& sourceRect, const QRectF& destRect, const AbstractCoordinatePlane& plane ) { QPointF result = plane.translate( origin ); result -= sourceRect.topLeft(); result.setX( result.x() / sourceRect.width() * destRect.width() ); result.setY( result.y() / sourceRect.height() * destRect.height() ); result += destRect.topLeft(); return result; } void RadarDiagram::setReverseData( bool val ) { d->reverseData = val; } bool RadarDiagram::reverseData() { return d->reverseData; } // local structure to remember the settings of a polygon inclusive the used color and pen. struct Polygon { QPolygonF polygon; QBrush brush; QPen pen; Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen) : polygon(polygon), brush(brush), pen(pen) {} }; void RadarDiagram::paint( PaintContext* ctx, bool calculateListAndReturnScale, qreal& newZoomX, qreal& newZoomY ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants(true) ) return; d->reverseMapper.clear(); const int rowCount = model()->rowCount( rootIndex() ); const int colCount = model()->columnCount( rootIndex() ); int iRow, iCol; const qreal min = dataBoundaries().first.y(); const qreal r = qAbs( min ) + dataBoundaries().second.y(); const qreal step = ( r - qAbs( min ) ) / ( numberOfGridRings() ); RadarCoordinatePlane* plane = dynamic_cast(ctx->coordinatePlane()); TextAttributes ta = plane->textAttributes(); QRectF fontRect = ctx->rectangle(); fontRect.setSize( QSizeF( fontRect.width(), step / 2.0 ) ); const qreal labelFontSize = fitFontSizeToGeometry( QString::fromLatin1( "TestXYWQgqy" ), ta.font(), fontRect, ta ); QFont labelFont = ta.font(); ctx->painter()->setPen( ta.pen() ); labelFont.setPointSizeF( labelFontSize ); const QFontMetricsF metric( labelFont ); const qreal labelHeight = metric.height(); QPointF offset; QRectF destRect = ctx->rectangle(); if ( ta.isVisible() ) { destRect.setY( destRect.y() + 2 * labelHeight ); destRect.setHeight( destRect.height() - 4 * labelHeight ); } if ( calculateListAndReturnScale ) { ctx->painter()->save(); // Check if all of the data value texts / data comments will fit // into the available space: d->labelPaintCache.clear(); ctx->painter()->save(); for ( iCol=0; iCol < colCount; ++iCol ) { for ( iRow=0; iRow < rowCount; ++iRow ) { QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked const qreal value = model()->data( index ).toReal(); QPointF point = scaleToRealPosition( QPointF( value, iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() ); d->addLabel( &d->labelPaintCache, index, nullptr, PositionPoints( point ), Position::Center, Position::Center, value ); } } ctx->painter()->restore(); const qreal oldZoomX = coordinatePlane()->zoomFactorX(); const qreal oldZoomY = coordinatePlane()->zoomFactorY(); newZoomX = oldZoomX; newZoomY = oldZoomY; if ( d->labelPaintCache.paintReplay.count() ) { QRectF txtRectF; d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF ); const QRect txtRect = txtRectF.toRect(); const QRect curRect = coordinatePlane()->geometry(); const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right() - txtRect.right() ); const qreal gapY = qMin( txtRect.top() - curRect.top(), curRect.bottom() - txtRect.bottom() ); newZoomX = oldZoomX; newZoomY = oldZoomY; if ( gapX < 0.0 ) newZoomX *= 1.0 + (gapX-1.0) / curRect.width(); if ( gapY < 0.0 ) newZoomY *= 1.0 + (gapY-1.0) / curRect.height(); } ctx->painter()->restore(); } else { // Iterate through data sets and create a list of polygons out of them. QList polygons; for ( iCol=0; iCol < colCount; ++iCol ) { //TODO(khz): As of yet RadarDiagram can not show per-segment line attributes // but it draws every polyline in one go - using one color. // This needs to be enhanced to allow for cell-specific settings // in the same way as LineDiagram does it. QPolygonF polygon; QPointF point0; for ( iRow=0; iRow < rowCount; ++iRow ) { QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked const qreal value = model()->data( index ).toReal(); QPointF point = scaleToRealPosition( QPointF( value, d->reverseData ? ( rowCount - iRow ) : iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() ); polygon.append( point ); if ( ! iRow ) point0= point; } if ( closeDatasets() && rowCount ) polygon.append( point0 ); QBrush brush = d->datasetAttrs( iCol, KChart::DatasetBrushRole ).value(); QPen p = d->datasetAttrs( iCol, KChart::DatasetPenRole ).value< QPen >(); if ( p.style() != Qt::NoPen ) { polygons.append( Polygon( polygon, brush, PrintingParameters::scalePen( p ) ) ); } } // first fill the areas with the brush-color and the defined alpha-value. if (d->fillAlpha > 0.0) { Q_FOREACH(const Polygon& p, polygons) { PainterSaver painterSaver( ctx->painter() ); ctx->painter()->setRenderHint ( QPainter::Antialiasing ); QBrush br = p.brush; QColor c = br.color(); c.setAlphaF(d->fillAlpha); br.setColor(c); ctx->painter()->setBrush( br ); ctx->painter()->setPen( p.pen ); ctx->painter()->drawPolygon( p.polygon ); } } // then draw the poly-lines. Q_FOREACH(const Polygon& p, polygons) { PainterSaver painterSaver( ctx->painter() ); ctx->painter()->setRenderHint ( QPainter::Antialiasing ); ctx->painter()->setBrush( p.brush ); ctx->painter()->setPen( p.pen ); ctx->painter()->drawPolyline( p.polygon ); } d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true ); } } void RadarDiagram::resize ( const QSizeF& size ) { AbstractPolarDiagram::resize( size ); } /*virtual*/ qreal RadarDiagram::valueTotals () const { return model()->rowCount(rootIndex()); } /*virtual*/ qreal RadarDiagram::numberOfValuesPerDataset() const { return model() ? model()->rowCount(rootIndex()) : 0.0; } /*virtual*/ qreal RadarDiagram::numberOfGridRings() const { return 5; // FIXME } void RadarDiagram::setCloseDatasets( bool closeDatasets ) { d->closeDatasets = closeDatasets; } bool RadarDiagram::closeDatasets() const { return d->closeDatasets; } qreal RadarDiagram::fillAlpha() const { return d->fillAlpha; } void RadarDiagram::setFillAlpha(qreal alphaF) { d->fillAlpha = alphaF; } void RadarDiagram::resizeEvent ( QResizeEvent*) { } diff --git a/src/KChart/Polar/KChartRadarDiagram.h b/src/KChart/Polar/KChartRadarDiagram.h index ba4b4ff..818727e 100644 --- a/src/KChart/Polar/KChartRadarDiagram.h +++ b/src/KChart/Polar/KChartRadarDiagram.h @@ -1,98 +1,101 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTRADARDIAGRAM_H #define KCHARTRADARDIAGRAM_H #include "KChartPosition.h" #include "KChartPolarDiagram.h" #include "KChartRadarCoordinatePlane.h" QT_BEGIN_NAMESPACE class QPolygonF; QT_END_NAMESPACE namespace KChart { /** * @brief RadarDiagram defines a common radar diagram */ class KCHART_EXPORT RadarDiagram : public AbstractPolarDiagram { Q_OBJECT Q_DISABLE_COPY( RadarDiagram ) KCHART_DECLARE_DERIVED_DIAGRAM( RadarDiagram, RadarCoordinatePlane ) public: explicit RadarDiagram( QWidget* parent = nullptr, RadarCoordinatePlane* plane = nullptr ); virtual ~RadarDiagram(); virtual void paint ( PaintContext* paintContext, bool calculateListAndReturnScale, qreal& newZoomX, qreal& newZoomY ); /** \reimpl */ void resize ( const QSizeF& area ) override; /** \reimpl */ qreal valueTotals () const override; /** \reimpl */ qreal numberOfValuesPerDataset() const override; /** \reimpl */ qreal numberOfGridRings() const override; /** * if val is true the diagram will mirror the diagram datapoints */ void setReverseData( bool val ); bool reverseData(); - virtual RadarDiagram * clone() const; + /** + * Creates an exact copy of this diagram. + */ + virtual RadarDiagram * clone() const; /** * Close each of the data series by connecting the last point to its * respective start point */ void setCloseDatasets( bool closeDatasets ); bool closeDatasets() const; /** * Fill the areas of the radar chart with there respective color defined * via KChart::DatasetBrushRole. The value defines the alpha of the * color to use. If set to 0.0 (the default) then the radar areas will * not be filled with any color. If set to 1.0 then the areas will be * solid filled and are not transparent. */ qreal fillAlpha() const; void setFillAlpha(qreal alphaF); protected: /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent ( QPaintEvent* ) override; void resizeEvent ( QResizeEvent* ) override; void paint ( PaintContext* paintContext ) override; }; // End of class RadarDiagram } #endif // KCHARTRADARDIAGRAM_H diff --git a/src/KChart/Polar/KChartRingDiagram.cpp b/src/KChart/Polar/KChartRingDiagram.cpp index 4814649..a3e177b 100644 --- a/src/KChart/Polar/KChartRingDiagram.cpp +++ b/src/KChart/Polar/KChartRingDiagram.cpp @@ -1,489 +1,470 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartRingDiagram.h" #include "KChartRingDiagram_p.h" #include "KChartAttributesModel.h" #include "KChartPaintContext.h" #include "KChartPainterSaver_p.h" #include "KChartPieAttributes.h" #include "KChartPolarCoordinatePlane_p.h" #include "KChartThreeDPieAttributes.h" #include "KChartDataValueAttributes.h" #include "KChartMath_p.h" #include using namespace KChart; RingDiagram::Private::Private() : relativeThickness( false ) , expandWhenExploded( false ) { } RingDiagram::Private::~Private() {} #define d d_func() RingDiagram::RingDiagram( QWidget* parent, PolarCoordinatePlane* plane ) : AbstractPieDiagram( new Private(), parent, plane ) { init(); } RingDiagram::~RingDiagram() { } void RingDiagram::init() { } -/** - * Creates an exact copy of this diagram. - */ RingDiagram * RingDiagram::clone() const { return new RingDiagram( new Private( *d ) ); } bool RingDiagram::compare( const RingDiagram* other ) const { if ( other == this ) return true; if ( ! other ) { return false; } return // compare the base class ( static_cast(this)->compare( other ) ) && // compare own properties (relativeThickness() == other->relativeThickness()) && (expandWhenExploded() == other->expandWhenExploded()); } void RingDiagram::setRelativeThickness( bool relativeThickness ) { d->relativeThickness = relativeThickness; } bool RingDiagram::relativeThickness() const { return d->relativeThickness; } void RingDiagram::setExpandWhenExploded( bool expand ) { d->expandWhenExploded = expand; } bool RingDiagram::expandWhenExploded() const { return d->expandWhenExploded; } const QPair RingDiagram::calculateDataBoundaries () const { if ( !checkInvariants( true ) ) return QPair( QPointF( 0, 0 ), QPointF( 0, 0 ) ); const PieAttributes attrs( pieAttributes() ); QPointF bottomLeft( 0, 0 ); QPointF topRight; // If we explode, we need extra space for the pie slice that has the largest explosion distance. if ( attrs.explode() ) { const int rCount = rowCount(); const int colCount = columnCount(); qreal maxExplode = 0.0; for ( int i = 0; i < rCount; ++i ) { qreal maxExplodeInThisRow = 0.0; for ( int j = 0; j < colCount; ++j ) { const PieAttributes columnAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); // checked maxExplodeInThisRow = qMax( maxExplodeInThisRow, columnAttrs.explodeFactor() ); } maxExplode += maxExplodeInThisRow; // FIXME: What if explode factor of inner ring is > 1.0 ? if ( !d->expandWhenExploded ) { break; } } // explode factor is relative to width (outer r - inner r) of one ring maxExplode /= ( rCount + 1); topRight = QPointF( 1.0 + maxExplode, 1.0 + maxExplode ); } else { topRight = QPointF( 1.0, 1.0 ); } return QPair( bottomLeft, topRight ); } void RingDiagram::paintEvent( QPaintEvent* ) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void RingDiagram::resizeEvent( QResizeEvent* ) { } void RingDiagram::paint( PaintContext* ctx ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants(true) ) return; d->reverseMapper.clear(); const PieAttributes attrs( pieAttributes() ); const int rCount = rowCount(); const int colCount = columnCount(); //QRectF contentsRect = PolarCoordinatePlane::Private::contentsRect( polarCoordinatePlane() ); QRectF contentsRect = ctx->rectangle(); if ( contentsRect.isEmpty() ) return; d->startAngles = QVector< QVector >( rCount, QVector( colCount ) ); d->angleLens = QVector< QVector >( rCount, QVector( colCount ) ); // compute position d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size // if the slices explode, we need to give them additional space => // make the basic size smaller qreal totalOffset = 0.0; for ( int i = 0; i < rCount; ++i ) { qreal maxOffsetInThisRow = 0.0; for ( int j = 0; j < colCount; ++j ) { const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); // checked //qDebug() << cellAttrs.explodeFactor(); const qreal explode = cellAttrs.explode() ? cellAttrs.explodeFactor() : 0.0; maxOffsetInThisRow = qMax( maxOffsetInThisRow, cellAttrs.gapFactor( false ) + explode ); } if ( !d->expandWhenExploded ) { maxOffsetInThisRow -= qreal( i ); } totalOffset += qMax( maxOffsetInThisRow, 0.0 ); // FIXME: What if explode factor of inner ring is > 1.0 ? //if ( !d->expandWhenExploded ) // break; } // explode factor is relative to width (outer r - inner r) of one ring if ( rCount > 0 ) totalOffset /= ( rCount + 1 ); d->size /= ( 1.0 + totalOffset ); qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 ); qreal y = ( contentsRect.height() == d->size ) ? 0.0 : ( ( contentsRect.height() - d->size ) / 2.0 ); d->position = QRectF( x, y, d->size, d->size ); d->position.translate( contentsRect.left(), contentsRect.top() ); const PolarCoordinatePlane * plane = polarCoordinatePlane(); QVariant vValY; d->forgetAlreadyPaintedDataValues(); for ( int iRow = 0; iRow < rCount; ++iRow ) { const qreal sum = valueTotals( iRow ); if ( sum == 0.0 ) //nothing to draw continue; qreal currentValue = plane ? plane->startPosition() : 0.0; const qreal sectorsPerValue = 360.0 / sum; for ( int iColumn = 0; iColumn < colCount; ++iColumn ) { // is there anything at all at this column? bool bOK; const qreal cellValue = qAbs( model()->data( model()->index( iRow, iColumn, rootIndex() ) ) // checked .toReal( &bOK ) ); if ( bOK ) { d->startAngles[ iRow ][ iColumn ] = currentValue; d->angleLens[ iRow ][ iColumn ] = cellValue * sectorsPerValue; } else { // mark as non-existent d->angleLens[ iRow ][ iColumn ] = 0.0; if ( iColumn > 0.0 ) { d->startAngles[ iRow ][ iColumn ] = d->startAngles[ iRow ][ iColumn - 1 ]; } else { d->startAngles[ iRow ][ iColumn ] = currentValue; } } currentValue = d->startAngles[ iRow ][ iColumn ] + d->angleLens[ iRow ][ iColumn ]; drawOneSlice( ctx->painter(), iRow, iColumn, granularity() ); } } } #if defined ( Q_WS_WIN) #define trunc(x) ((int)(x)) #endif -/** - \param painter the QPainter to draw in - \param dataset the dataset to draw the slice for - \param slice the slice to draw - */ void RingDiagram::drawOneSlice( QPainter* painter, uint dataset, uint slice, qreal granularity ) { // Is there anything to draw at all? const qreal angleLen = d->angleLens[ dataset ][ slice ]; if ( angleLen ) { drawPieSurface( painter, dataset, slice, granularity ); } } void RingDiagram::resize( const QSizeF& size) { AbstractPieDiagram ::resize( size ); } -/** - Internal method that draws the top surface of one of the slices in a ring chart. - - \param painter the QPainter to draw in - \param dataset the dataset to draw the slice for - \param slice the slice to draw - */ void RingDiagram::drawPieSurface( QPainter* painter, uint dataset, uint slice, qreal granularity ) { // Is there anything to draw at all? qreal angleLen = d->angleLens[ dataset ][ slice ]; if ( angleLen ) { qreal startAngle = d->startAngles[ dataset ][ slice ]; QModelIndex index( model()->index( dataset, slice, rootIndex() ) ); // checked const PieAttributes attrs( pieAttributes( index ) ); const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) ); const int rCount = rowCount(); const int colCount = columnCount(); int iPoint = 0; QRectF drawPosition = d->position; painter->setRenderHint ( QPainter::Antialiasing ); QBrush br = brush( index ); if ( threeDAttrs.isEnabled() ) { br = threeDAttrs.threeDBrush( br, drawPosition ); } painter->setBrush( br ); painter->setPen( pen( index ) ); if ( angleLen == 360 ) { // full circle, avoid nasty line in the middle // FIXME: Draw a complete ring here //painter->drawEllipse( drawPosition ); } else { bool perfectMatch = false; qreal circularGap = 0.0; if ( attrs.gapFactor( true ) > 0.0 ) { // FIXME: Measure in degrees! circularGap = attrs.gapFactor( true ); } QPolygonF poly; qreal degree = 0; qreal actualStartAngle = startAngle + circularGap; qreal actualAngleLen = angleLen - 2 * circularGap; qreal totalRadialExplode = 0.0; qreal maxRadialExplode = 0.0; qreal totalRadialGap = 0.0; qreal maxRadialGap = 0.0; for ( uint i = rCount - 1; i > dataset; --i ) { qreal maxRadialExplodeInThisRow = 0.0; qreal maxRadialGapInThisRow = 0.0; for ( int j = 0; j < colCount; ++j ) { const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); // checked if ( d->expandWhenExploded ) { maxRadialGapInThisRow = qMax( maxRadialGapInThisRow, cellAttrs.gapFactor( false ) ); } // Don't use a gap for the very inner circle if ( cellAttrs.explode() && d->expandWhenExploded ) { maxRadialExplodeInThisRow = qMax( maxRadialExplodeInThisRow, cellAttrs.explodeFactor() ); } } maxRadialExplode += maxRadialExplodeInThisRow; maxRadialGap += maxRadialGapInThisRow; // FIXME: What if explode factor of inner ring is > 1.0 ? //if ( !d->expandWhenExploded ) // break; } totalRadialGap = maxRadialGap + attrs.gapFactor( false ); totalRadialExplode = attrs.explode() ? maxRadialExplode + attrs.explodeFactor() : maxRadialExplode; while ( degree <= actualAngleLen ) { const QPointF p = pointOnEllipse( drawPosition, dataset, slice, false, actualStartAngle + degree, totalRadialGap, totalRadialExplode ); poly.append( p ); degree += granularity; iPoint++; } if ( ! perfectMatch ) { poly.append( pointOnEllipse( drawPosition, dataset, slice, false, actualStartAngle + actualAngleLen, totalRadialGap, totalRadialExplode ) ); iPoint++; } // The center point of the inner brink const QPointF innerCenterPoint( poly[ int(iPoint / 2) ] ); actualStartAngle = startAngle + circularGap; actualAngleLen = angleLen - 2 * circularGap; degree = actualAngleLen; const int lastInnerBrinkPoint = iPoint; while ( degree >= 0 ) { poly.append( pointOnEllipse( drawPosition, dataset, slice, true, actualStartAngle + degree, totalRadialGap, totalRadialExplode ) ); perfectMatch = (degree == 0); degree -= granularity; iPoint++; } // if necessary add one more point to fill the last small gap if ( ! perfectMatch ) { poly.append( pointOnEllipse( drawPosition, dataset, slice, true, actualStartAngle, totalRadialGap, totalRadialExplode ) ); iPoint++; } // The center point of the outer brink const QPointF outerCenterPoint( poly[ lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2) ] ); //qDebug() << poly; //find the value and paint it //fix value position const qreal sum = valueTotals( dataset ); painter->drawPolygon( poly ); d->reverseMapper.addPolygon( index.row(), index.column(), poly ); const QPointF centerPoint = (innerCenterPoint + outerCenterPoint) / 2.0; const PainterSaver ps( painter ); const TextAttributes ta = dataValueAttributes( index ).textAttributes(); if ( !ta.hasRotation() && autoRotateLabels() ) { const QPointF& p1 = poly.last(); const QPointF& p2 = poly[ lastInnerBrinkPoint ]; const QLineF line( p1, p2 ); // TODO: do the label rotation like in PieDiagram const qreal angle = line.dx() == 0 ? 0.0 : atan( line.dy() / line.dx() ); painter->translate( centerPoint ); painter->rotate( angle / 2.0 / 3.141592653589793 * 360.0 ); painter->translate( -centerPoint ); } paintDataValueText( painter, index, centerPoint, angleLen*sum / 360 ); } } } -/** - * Auxiliary method returning a point to a given boundary - * rectangle of the enclosed ellipse and an angle. - */ QPointF RingDiagram::pointOnEllipse( const QRectF& rect, int dataset, int slice, bool outer, qreal angle, qreal totalGapFactor, qreal totalExplodeFactor ) { qreal angleLen = d->angleLens[ dataset ][ slice ]; qreal startAngle = d->startAngles[ dataset ][ slice ]; const int rCount = rowCount() * 2; qreal level = outer ? ( rCount - dataset - 1 ) + 2 : ( rCount - dataset - 1 ) + 1; const qreal offsetX = rCount > 0 ? level * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0; const qreal offsetY = rCount > 0 ? level * rect.height() / ( ( rCount + 1 ) * 2 ) : 0.0; const qreal centerOffsetX = rCount > 0 ? totalExplodeFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0; const qreal centerOffsetY = rCount > 0 ? totalExplodeFactor * rect.height() / ( ( rCount + 1 ) * 2 ) : 0.0; const qreal gapOffsetX = rCount > 0 ? totalGapFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0; const qreal gapOffsetY = rCount > 0 ? totalGapFactor * rect.height() / ( ( rCount + 1 ) * 2 ) : 0.0; qreal explodeAngleRad = DEGTORAD( angle ); qreal cosAngle = cos( explodeAngleRad ); qreal sinAngle = -sin( explodeAngleRad ); qreal explodeAngleCenterRad = DEGTORAD( startAngle + angleLen / 2.0 ); qreal cosAngleCenter = cos( explodeAngleCenterRad ); qreal sinAngleCenter = -sin( explodeAngleCenterRad ); return QPointF( ( offsetX + gapOffsetX ) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(), ( offsetY + gapOffsetY ) * sinAngle + centerOffsetY * sinAngleCenter + rect.center().y() ); } /*virtual*/ qreal RingDiagram::valueTotals() const { const int rCount = rowCount(); const int colCount = columnCount(); qreal total = 0.0; for ( int i = 0; i < rCount; ++i ) { for ( int j = 0; j < colCount; ++j ) { total += qAbs( model()->data( model()->index( i, j, rootIndex() ) ).toReal() ); // checked } } return total; } qreal RingDiagram::valueTotals( int dataset ) const { Q_ASSERT( dataset < model()->rowCount() ); const int colCount = columnCount(); qreal total = 0.0; for ( int j = 0; j < colCount; ++j ) { total += qAbs( model()->data( model()->index( dataset, j, rootIndex() ) ).toReal() ); // checked } return total; } /*virtual*/ qreal RingDiagram::numberOfValuesPerDataset() const { return model() ? model()->columnCount( rootIndex() ) : 0.0; } qreal RingDiagram::numberOfDatasets() const { return model() ? model()->rowCount( rootIndex() ) : 0.0; } /*virtual*/ qreal RingDiagram::numberOfGridRings() const { return 1; } diff --git a/src/KChart/Polar/KChartRingDiagram.h b/src/KChart/Polar/KChartRingDiagram.h index 8f6fbfd..ebdbeb5 100644 --- a/src/KChart/Polar/KChartRingDiagram.h +++ b/src/KChart/Polar/KChartRingDiagram.h @@ -1,89 +1,112 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KCHARTRINGDIAGRAM_H #define KCHARTRINGDIAGRAM_H #include "KChartAbstractPieDiagram.h" namespace KChart { /** * @brief RingDiagram defines a common ring diagram */ class KCHART_EXPORT RingDiagram : public AbstractPieDiagram { Q_OBJECT Q_DISABLE_COPY( RingDiagram ) KCHART_DECLARE_DERIVED_DIAGRAM( RingDiagram, PolarCoordinatePlane ) public: explicit RingDiagram( QWidget* parent = nullptr, PolarCoordinatePlane* plane = nullptr ); virtual ~RingDiagram(); protected: // Implement AbstractDiagram /** \reimpl */ void paint( PaintContext* paintContext ) override; public: /** \reimpl */ void resize( const QSizeF& area ) override; // Implement AbstractPolarDiagram /** \reimpl */ qreal valueTotals() const override; /** \reimpl */ qreal numberOfValuesPerDataset() const override; qreal numberOfDatasets() const override; /** \reimpl */ qreal numberOfGridRings() const override; qreal valueTotals( int dataset ) const; - virtual RingDiagram * clone() const; + + /** + * Creates an exact copy of this diagram. + */ + virtual RingDiagram * clone() const; /** * Returns true if both diagrams have the same settings. */ bool compare( const RingDiagram* other ) const; void setRelativeThickness( bool relativeThickness ); bool relativeThickness() const; virtual void setExpandWhenExploded( bool expand ); virtual bool expandWhenExploded() const; protected: /** \reimpl */ const QPair calculateDataBoundaries() const override; void paintEvent( QPaintEvent* ) override; void resizeEvent( QResizeEvent* ) override; private: - void drawOneSlice( QPainter* painter, uint dataset, uint slice, qreal granularity ); - void drawPieSurface( QPainter* painter, uint dataset, uint slice, qreal granularity ); - QPointF pointOnEllipse( const QRectF& rect, int dataset, int slice, bool outer, qreal angle, + + /** + \param painter the QPainter to draw in + \param dataset the dataset to draw the slice for + \param slice the slice to draw + */ + void drawOneSlice( QPainter* painter, uint dataset, uint slice, qreal granularity ); + + /** + Internal method that draws the top surface of one of the slices in a ring chart. + + \param painter the QPainter to draw in + \param dataset the dataset to draw the slice for + \param slice the slice to draw + */ + void drawPieSurface( QPainter* painter, uint dataset, uint slice, qreal granularity ); + + /** + * Auxiliary method returning a point to a given boundary + * rectangle of the enclosed ellipse and an angle. + */ + QPointF pointOnEllipse( const QRectF& rect, int dataset, int slice, bool outer, qreal angle, qreal totalGapFactor, qreal totalExplodeFactor ); }; // End of class RingDiagram } #endif // KCHARTRINGDIAGRAM_H