/**************************************************************************** ** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved. ** ** This file is part of the KD Chart library. ** ** Licensees holding valid commercial KD Chart licenses may use this file in ** accordance with the KD Chart Commercial License Agreement provided with ** the Software. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 and version 3 as published by the ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** Contact info@kdab.com if any conditions of this licensing are not ** clear to you. ** **********************************************************************/ #ifndef KDCHARTABSTRACTDIAGRAM_P_H #define KDCHARTABSTRACTDIAGRAM_P_H // // W A R N I N G // ------------- // // This file is not part of the KD Chart API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "KDChartAbstractDiagram.h" #include "KDChartAbstractCoordinatePlane.h" #include "KDChartDataValueAttributes.h" #include "KDChartBackgroundAttributes" #include "KDChartRelativePosition.h" #include "KDChartPosition.h" #include "KDChartPainterSaver_p.h" #include "KDChartPaintContext.h" #include "KDChartPrintingParameters.h" #include "KDChartChart.h" #include #include "Scenery/ReverseMapper.h" #include #include #include #include #include #include #include #include #include #include namespace KDChart { class AttributesModel; class DataValueTextInfo { public: DataValueTextInfo(){} DataValueTextInfo( const QModelIndex& _index, const DataValueAttributes& _attrs, const QPointF& _pos, const QPointF& _markerPos, double _value ) :index( _index), attrs( _attrs ), pos( _pos ), markerPos( _markerPos ), value( _value ) {} DataValueTextInfo( const DataValueTextInfo& other ) :index( other.index ), attrs( other.attrs ), pos( other.pos ), markerPos( other.markerPos ), value( other.value ) {} QModelIndex index; DataValueAttributes attrs; QPointF pos; QPointF markerPos; double value; }; typedef QVector DataValueTextInfoList; typedef QVectorIterator DataValueTextInfoListIterator; /** * \internal */ class KDChart::AbstractDiagram::Private { friend class AbstractDiagram; public: explicit Private(); virtual ~Private(); Private( const Private& rhs ); void setAttributesModel( AttributesModel* ); bool usesExternalAttributesModel()const; // FIXME: Optimize if necessary virtual qreal calcPercentValue( const QModelIndex & index ) { qreal sum = 0.0; for ( int col = 0; col < attributesModel->columnCount( QModelIndex() ); col++ ) sum += attributesModel->data( attributesModel->index( index.row(), col, QModelIndex() ) ).toDouble(); if ( sum == 0.0 ) return 0.0; return attributesModel->data( attributesModel->mapFromSource( index ) ).toDouble() / sum * 100.0; } void appendDataValueTextInfoToList( AbstractDiagram * diagram, DataValueTextInfoList & list, const QModelIndex & index, const CartesianDiagramDataCompressor::CachePosition * position, const PositionPoints& points, const Position& autoPositionPositive, const Position& autoPositionNegative, const qreal value ) { CartesianDiagramDataCompressor::DataValueAttributesList allAttrs( aggregatedAttrs( diagram, index, position ) ); QMap::const_iterator i; for (i = allAttrs.constBegin(); i != allAttrs.constEnd(); ++i){ if( i.value().isVisible() ){ const bool bValueIsPositive = (value >= 0.0); RelativePosition relPos( i.value().position( bValueIsPositive ) ); relPos.setReferencePoints( points ); if( relPos.referencePosition().isUnknown() ) relPos.setReferencePosition( bValueIsPositive ? autoPositionPositive : autoPositionNegative ); const QPointF referencePoint = relPos.referencePoint(); if( diagram->coordinatePlane()->isVisiblePoint( referencePoint ) ){ const qreal fontHeight = cachedFontMetrics( i.value().textAttributes(). calculatedFont( plane, KDChartEnums::MeasureOrientationMinimum ), diagram )->height(); // Note: When printing data value texts the font height is used as reference size for both, // horizontal and vertical padding, if the respective padding's Measure is using // automatic reference area detection. QSizeF relativeMeasureSize( fontHeight, fontHeight ); //qDebug()<<"fontHeight"<= decimalDigits ; --i ) { count += 1; int lastval = QString( digits.data() [i] ).toInt(); int val = QString( digits.data() [i-1] ) .toInt(); if ( lastval >= 5 ) { val += 1; digits.replace( digits.length() - count,1 , QString::number( val ) ); } } digits.truncate( decimalDigits ); num.append( QLatin1Char( '.' ) + digits ); return num; } void clearListOfAlreadyDrawnDataValueTexts() { alreadyDrawnDataValueTexts.clear(); } void paintDataValueTextsAndMarkers( AbstractDiagram* diag, PaintContext* ctx, const DataValueTextInfoList & list, bool paintMarkers, bool justCalculateRect=false, QRectF* cumulatedBoundingRect=0 ) { const PainterSaver painterSaver( ctx->painter() ); ctx->painter()->setClipping( false ); if( paintMarkers && ! justCalculateRect ) { DataValueTextInfoListIterator it( list ); while ( it.hasNext() ) { const DataValueTextInfo& info = it.next(); diag->paintMarker( ctx->painter(), info.index, info.markerPos ); } } DataValueTextInfoListIterator it( list ); Measure m( 18.0, KDChartEnums::MeasureCalculationModeRelative, KDChartEnums::MeasureOrientationMinimum ); m.setReferenceArea( ctx->coordinatePlane() ); TextAttributes ta; ta.setFontSize( m ); m.setAbsoluteValue( 6.0 ); ta.setMinimalFontSize( m ); clearListOfAlreadyDrawnDataValueTexts(); while ( it.hasNext() ) { const DataValueTextInfo& info = it.next(); paintDataValueText( diag, ctx->painter(), info.index, info.pos, info.value, justCalculateRect, cumulatedBoundingRect ); const QString comment = info.index.data( KDChart::CommentRole ).toString(); if( comment.isEmpty() ) continue; TextBubbleLayoutItem item( comment, ta, ctx->coordinatePlane()->parent(), KDChartEnums::MeasureOrientationMinimum, Qt::AlignHCenter|Qt::AlignVCenter ); const QRect rect( info.pos.toPoint(), item.sizeHint() ); if( justCalculateRect && cumulatedBoundingRect ){ (*cumulatedBoundingRect) |= rect; }else{ item.setGeometry( rect ); item.paint( ctx->painter() ); // Return the cumulatedBoundingRect if asked for if(cumulatedBoundingRect) (*cumulatedBoundingRect) |= rect; } } } void paintDataValueText( const AbstractDiagram* diag, QPainter* painter, const QModelIndex& index, const QPointF& pos, double value, bool justCalculateRect=false, QRectF* cumulatedBoundingRect=0 ) { const DataValueAttributes a( diag->dataValueAttributes( index ) ); if ( !a.isVisible() ) return; if ( a.usePercentage() ) value = calcPercentValue( index ); // handle decimal digits int decimalDigits = a.decimalDigits(); int decimalPos = QString::number( value ).indexOf( QLatin1Char( '.' ) ); QString roundedValue; if ( a.dataLabel().isNull() ){ if ( decimalPos > 0 && value != 0 ) roundedValue = roundValues ( value, decimalPos, decimalDigits ); else roundedValue = QString::number( value ); }else{ roundedValue = a.dataLabel(); } // handle prefix and suffix if ( !a.prefix().isNull() ) roundedValue.prepend( a.prefix() ); if ( !a.suffix().isNull() ) roundedValue.append( a.suffix() ); paintDataValueText( diag, painter, a, pos, roundedValue, value >= 0.0, justCalculateRect, cumulatedBoundingRect ); } void paintDataValueText( const AbstractDiagram* diag, QPainter* painter, const DataValueAttributes& attrs, const QPointF& pos, QString text, bool valueIsPositive, bool justCalculateRect=false, QRectF* cumulatedBoundingRect=0 ) { if ( !attrs.isVisible() ) return; const TextAttributes ta( attrs.textAttributes() ); if ( ta.isVisible() ) { /* for debugging: PainterSaver painterSaver( painter ); painter->setPen( Qt::black ); painter->drawLine( pos - QPointF( 2,2), pos + QPointF( 2,2) ); painter->drawLine( pos - QPointF(-2,2), pos + QPointF(-2,2) ); */ QTextDocument doc; if( Qt::mightBeRichText( text ) ) doc.setHtml( text ); else doc.setPlainText( text ); const RelativePosition relPos( attrs.position( valueIsPositive ) ); //const Qt::Alignment alignBottomLeft = Qt::AlignBottom | Qt::AlignLeft; const QFont calculatedFont( ta.calculatedFont( plane, KDChartEnums::MeasureOrientationMinimum ) ); // note: We can not use boundingRect() to retrieve the width, as that returnes a too small value const QSizeF plainSize( cachedFontMetrics( calculatedFont, painter->device() )->width( doc.toPlainText() ), cachedFontMetrics( calculatedFont, painter->device() )->boundingRect( doc.toPlainText() ).height() ); // FIXME draw the non-text bits, background, etc if ( attrs.showRepetitiveDataLabels() || pos.x() <= lastX || lastRoundedValue != text ) { //qDebug() << text; //Check if there is only one and only one pie. //If not then update lastRoundedValue for further checking. if(!(diag->model()->rowCount() == 1)) lastRoundedValue = text; lastX = pos.x(); const PainterSaver painterSaver( painter ); painter->setPen( PrintingParameters::scalePen( ta.pen() ) ); doc.setDefaultFont( calculatedFont ); QAbstractTextDocumentLayout::PaintContext context; context.palette = diag->palette(); context.palette.setColor(QPalette::Text, ta.pen().color() ); BackgroundAttributes back(attrs.backgroundAttributes()); if(back.isVisible()) { QTextBlockFormat fmt; fmt.setBackground(back.brush()); QTextCursor cursor(&doc); cursor.setPosition(0); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 1); cursor.mergeBlockFormat(fmt); } QAbstractTextDocumentLayout* const layout = doc.documentLayout(); painter->translate( pos ); painter->rotate( ta.rotation() ); qreal dx = 0.0; qreal dy = 0.0; const Qt::Alignment alignTopLeft = (Qt::AlignLeft | Qt::AlignTop); if( (relPos.alignment() & alignTopLeft) != alignTopLeft ){ if( relPos.alignment() & Qt::AlignRight ) dx = - plainSize.width(); else if( relPos.alignment() & Qt::AlignHCenter ) dx = - 0.5 * plainSize.width(); if( relPos.alignment() & Qt::AlignBottom ) dy = - plainSize.height(); else if( relPos.alignment() & Qt::AlignVCenter ) dy = - 0.5 * plainSize.height(); } bool drawIt = true; // note: This flag can be set differently for every label text! // In theory a user could e.g. have some small red text on one of the // values that she wants to have written in any case - so we just // do not test if such texts would cover some of the others. if( ! attrs.showOverlappingDataLabels() ){ const QRectF br( layout->blockBoundingRect( doc.begin() ) ); qreal radRot = DEGTORAD( - ((ta.rotation() < 0) ? ta.rotation()+360 : ta.rotation()) ); //qDebug() << radRot; qreal cosRot = cos( radRot ); qreal sinRot = sin( radRot ); QPolygon pr( br.toRect(), true ); // YES, people, the following stuff NEEDS to be done that way! // Otherwise we will not get the texts' individual rotation // and/or the shifting of the texts correctly. // Just believe me - I did tests .. :-) (khz, 2008-02-19) for( int i=0; i(pos.x() + x*cosRot + y*sinRot), static_cast(pos.y() - x*sinRot + y*cosRot)); } KDAB_FOREACH( QPolygon oldPoly, alreadyDrawnDataValueTexts ) { if( ! oldPoly.intersected( pr ).isEmpty() ) drawIt = false; } if( drawIt ) alreadyDrawnDataValueTexts << pr; } if( drawIt ){ QRectF rect = layout->frameBoundingRect(doc.rootFrame()); rect.moveTo(pos.x()+dx, pos.y()+dy); if( justCalculateRect && cumulatedBoundingRect ){ (*cumulatedBoundingRect) |= rect; }else{ painter->translate( QPointF( dx, dy ) ); layout->draw( painter, context ); // Return the cumulatedBoundingRect if asked for if(cumulatedBoundingRect) (*cumulatedBoundingRect) |= rect; } } } } } virtual QModelIndex indexAt( const QPoint& point ) const { QModelIndexList l = indexesAt( point ); qSort( l ); if ( !l.isEmpty() ) return l.first(); else return QModelIndex(); } QModelIndexList indexesAt( const QPoint& point ) const { return reverseMapper.indexesAt( point ); // which could be empty } QModelIndexList indexesIn( const QRect& rect ) const { return reverseMapper.indexesIn( rect ); } virtual CartesianDiagramDataCompressor::DataValueAttributesList aggregatedAttrs( AbstractDiagram * diagram, const QModelIndex & index, const CartesianDiagramDataCompressor::CachePosition * position ) const { Q_UNUSED( position ); // used by cartesian diagrams only CartesianDiagramDataCompressor::DataValueAttributesList allAttrs; allAttrs[index] = diagram->dataValueAttributes( index ); return allAttrs; } /** * Sets arbitrary attributes of a data set. */ void setDatasetAttrs( int dataset, QVariant data, DisplayRoles role ) { // To store attributes for a dataset, we use the first column // that's associated with it. (i.e., with a dataset dimension // of two, the column of the keys). In most cases however, there's // only one data dimension, and thus also only one column per data set. int column = dataset * datasetDimension; attributesModel->setHeaderData( column, Qt::Horizontal, data, role ); } /** * Retrieves arbitrary attributes of a data set. */ QVariant datasetAttrs( int dataset, DisplayRoles role ) const { // See setDataSetAttrs for explanation of column int column = dataset * datasetDimension; return attributesModel->headerData( column, Qt::Horizontal, role ); } /** * Resets an attribute of a dataset back to its default. */ void resetDatasetAttrs( int dataset, DisplayRoles role ) { // See setDataSetAttrs for explanation of column int column = dataset * datasetDimension; attributesModel->resetHeaderData( column, Qt::Horizontal, role ); } protected: void init(); void init( AbstractCoordinatePlane* plane ); QPointer plane; mutable QModelIndex attributesModelRootIndex; QPointer attributesModel; bool allowOverlappingDataValueTexts; bool antiAliasing; bool percent; int datasetDimension; mutable QPair databoundaries; mutable bool databoundariesDirty; ReverseMapper reverseMapper; QMap< Qt::Orientation, QString > unitSuffix; QMap< Qt::Orientation, QString > unitPrefix; QMap< int, QMap< Qt::Orientation, QString > > unitSuffixMap; QMap< int, QMap< Qt::Orientation, QString > > unitPrefixMap; QList< QPolygon > alreadyDrawnDataValueTexts; private: QString lastRoundedValue; qreal lastX; QFontMetrics mCachedFontMetrics; QFont mCachedFont; QPaintDevice * mCachedPaintDevice; }; inline AbstractDiagram::AbstractDiagram( Private * p ) : _d( p ) { init(); } inline AbstractDiagram::AbstractDiagram( Private * p, QWidget* parent, AbstractCoordinatePlane* plane ) : QAbstractItemView( parent ), _d( p ) { _d->init( plane ); init(); } class LineAttributesInfo { public : LineAttributesInfo() {} LineAttributesInfo( const QModelIndex _index, const QPointF& _value, const QPointF& _nextValue ) :index( _index ), value ( _value ), nextValue ( _nextValue ) {} QModelIndex index; QPointF value; QPointF nextValue; }; typedef QVector LineAttributesInfoList; typedef QVectorIterator LineAttributesInfoListIterator; } #endif /* KDCHARTDIAGRAM_P_H */