KD Chart 2  [rev.2.5]
KDChartAbstractDiagram_p.cpp
Go to the documentation of this file.
00001 //
00002 //  W A R N I N G
00003 //  -------------
00004 //
00005 // This file is not part of the KD Chart API.  It exists purely as an
00006 // implementation detail.  This header file may change from version to
00007 // version without notice, or even be removed.
00008 //
00009 // We mean it.
00010 //
00011 
00012 #include "KDChartAbstractDiagram_p.h"
00013 
00014 #include "KDChartFrameAttributes.h"
00015 #include "KDChartPainterSaver_p.h"
00016 
00017 #include <QAbstractTextDocumentLayout>
00018 #include <QTextBlock>
00019 #include <QApplication>
00020 
00021 #include <KDABLibFakes>
00022 
00023 
00024 using namespace KDChart;
00025 
00026 LabelPaintInfo::LabelPaintInfo()
00027 {
00028 }
00029 
00030 LabelPaintInfo::LabelPaintInfo( const QModelIndex& _index, const DataValueAttributes& _attrs,
00031                                 const QPainterPath& _labelArea, const QPointF& _markerPos,
00032                                 bool _isValuePositive, const QString& _value )
00033     : index( _index )
00034     , attrs( _attrs )
00035     , labelArea( _labelArea )
00036     , markerPos( _markerPos )
00037     , isValuePositive( _isValuePositive )
00038     , value( _value )
00039 {
00040 }
00041 
00042 LabelPaintInfo::LabelPaintInfo( const LabelPaintInfo& other )
00043     : index( other.index )
00044     , attrs( other.attrs )
00045     , labelArea( other.labelArea )
00046     , markerPos( other.markerPos )
00047     , isValuePositive( other.isValuePositive )
00048     , value( other.value )
00049 {
00050 }
00051 
00052 AbstractDiagram::Private::Private()
00053   : diagram( 0 )
00054   , doDumpPaintTime( false )
00055   , plane( 0 )
00056   , attributesModel( new PrivateAttributesModel(0,0) )
00057   , allowOverlappingDataValueTexts( false )
00058   , antiAliasing( true )
00059   , percent( false )
00060   , datasetDimension( 1 )
00061   , databoundariesDirty( true )
00062   , mCachedFontMetrics( QFontMetrics( qApp->font() ) )
00063 {
00064 }
00065 
00066 AbstractDiagram::Private::~Private()
00067 {
00068   if( attributesModel && qobject_cast<PrivateAttributesModel*>(attributesModel) )
00069     delete attributesModel;
00070 }
00071 
00072 void AbstractDiagram::Private::init()
00073 {
00074 }
00075 
00076 void AbstractDiagram::Private::init( AbstractCoordinatePlane* newPlane )
00077 {
00078     plane = newPlane;
00079 }
00080 
00081 bool AbstractDiagram::Private::usesExternalAttributesModel() const
00082 {
00083     return ( ! attributesModel.isNull() ) &&
00084            ( ! qobject_cast<PrivateAttributesModel*>(attributesModel) );
00085 }
00086 
00087 void AbstractDiagram::Private::setAttributesModel( AttributesModel* amodel )
00088 {
00089     if( !attributesModel.isNull() &&
00090         qobject_cast<PrivateAttributesModel*>(attributesModel) ) {
00091         delete attributesModel;
00092     }
00093     attributesModel = amodel;
00094 }
00095 
00096 AbstractDiagram::Private::Private( const AbstractDiagram::Private& rhs ) :
00097     diagram( 0 ),
00098     doDumpPaintTime( rhs.doDumpPaintTime ),
00099     // Do not copy the plane
00100     plane( 0 ),
00101     attributesModelRootIndex( QModelIndex() ),
00102     attributesModel( rhs.attributesModel ),
00103     allowOverlappingDataValueTexts( rhs.allowOverlappingDataValueTexts ),
00104     antiAliasing( rhs.antiAliasing ),
00105     percent( rhs.percent ),
00106     datasetDimension( rhs.datasetDimension ),
00107     mCachedFontMetrics( rhs.cachedFontMetrics() )
00108 {
00109     attributesModel = new PrivateAttributesModel( 0, 0);
00110     attributesModel->initFrom( rhs.attributesModel );
00111 }
00112 
00113 // FIXME: Optimize if necessary
00114 qreal AbstractDiagram::Private::calcPercentValue( const QModelIndex & index ) const
00115 {
00116     qreal sum = 0.0;
00117     for ( int col = 0; col < attributesModel->columnCount( QModelIndex() ); col++ )
00118         sum += attributesModel->data( attributesModel->index( index.row(), col, QModelIndex() ) ).toReal(); // checked
00119     if ( sum == 0.0 )
00120         return 0.0;
00121     return attributesModel->data( attributesModel->mapFromSource( index ) ).toReal() / sum * 100.0;
00122 }
00123 
00124 void AbstractDiagram::Private::addLabel(
00125     LabelPaintCache* cache,
00126     const QModelIndex& index,
00127     const CartesianDiagramDataCompressor::CachePosition* position,
00128     const PositionPoints& points,
00129     const Position& autoPositionPositive, const Position& autoPositionNegative,
00130     const qreal value, qreal favoriteAngle /* = 0.0 */ )
00131 {
00132     CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs(
00133         aggregatedAttrs( index, position ) );
00134 
00135     QMap<QModelIndex, DataValueAttributes>::const_iterator it;
00136     for ( it = allAttrs.constBegin(); it != allAttrs.constEnd(); ++it ) {
00137         DataValueAttributes dva = it.value();
00138         if ( !dva.isVisible() ) {
00139             continue;
00140         }
00141 
00142         const bool isPositive = ( value >= 0.0 );
00143 
00144         RelativePosition relPos( dva.position( isPositive ) );
00145         relPos.setReferencePoints( points );
00146         if ( relPos.referencePosition().isUnknown() ) {
00147             relPos.setReferencePosition( isPositive ? autoPositionPositive : autoPositionNegative );
00148         }
00149         const QPointF referencePoint = relPos.referencePoint();
00150         if ( !diagram->coordinatePlane()->isVisiblePoint( referencePoint ) ) {
00151             continue;
00152         }
00153 
00154         const qreal fontHeight = cachedFontMetrics( dva.textAttributes().
00155                 calculatedFont( plane, KDChartEnums::MeasureOrientationMinimum ), diagram )->height();
00156         // Note: When printing data value texts and padding's Measure is using automatic reference area
00157         //       detection, the font height is used as reference size for both horizontal and vertical
00158         //       padding.
00159         QSizeF relativeMeasureSize( fontHeight, fontHeight );
00160 
00161         if ( !dva.textAttributes().hasRotation() ) {
00162             TextAttributes ta = dva.textAttributes();
00163             ta.setRotation( favoriteAngle );
00164             dva.setTextAttributes( ta );
00165         }
00166 
00167         // get the size of the label text using a subset of the information going into the final layout
00168         const QString text = formatDataValueText( dva, index, value );
00169         QTextDocument doc;
00170         doc.setDocumentMargin( 0 );
00171         if ( Qt::mightBeRichText( text ) ) {
00172             doc.setHtml( text );
00173         } else {
00174             doc.setPlainText( text );
00175         }
00176         const QFont calculatedFont( dva.textAttributes()
00177                                     .calculatedFont( plane, KDChartEnums::MeasureOrientationMinimum ) );
00178         doc.setDefaultFont( calculatedFont );
00179 
00180         const QRectF plainRect = doc.documentLayout()->frameBoundingRect( doc.rootFrame() );
00181 
00217         QTransform transform;
00218         {
00219             // move to the general area where the label should be
00220             QPointF calcPoint = relPos.calculatedPoint( relativeMeasureSize );
00221             transform.translate( calcPoint.x(), calcPoint.y() );
00222             // align the text rect; find out by how many half-widths / half-heights to move.
00223             int dx = -1;
00224             if ( relPos.alignment() & Qt::AlignLeft ) {
00225                 dx -= 1;
00226             } else if ( relPos.alignment() & Qt::AlignRight ) {
00227                  dx += 1;
00228             }
00229 
00230             int dy = -1;
00231             if ( relPos.alignment() & Qt::AlignTop ) {
00232                 dy -= 1;
00233             } else if ( relPos.alignment() & Qt::AlignBottom ) {
00234                 dy += 1;
00235             }
00236             transform.translate( qreal( dx ) * plainRect.width() * 0.5,
00237                                  qreal( dy ) * plainRect.height() * 0.5 );
00238 
00239             // rotate the text rect around its center
00240             transform.translate( plainRect.center().x(), plainRect.center().y() );
00241             int rotation = dva.textAttributes().rotation();
00242             if ( !isPositive && dva.mirrorNegativeValueTextRotation() ) {
00243                 rotation *= -1;
00244             }
00245             transform.rotate( rotation );
00246             transform.translate( -plainRect.center().x(), -plainRect.center().y() );
00247         }
00248 
00249         QPainterPath labelArea;
00250         labelArea.addPolygon( transform.mapToPolygon( plainRect.toRect() ) );
00251         labelArea.closeSubpath();
00252 
00253         // store the label geometry and auxiliary data
00254         cache->paintReplay.append( LabelPaintInfo( it.key(), dva, labelArea,
00255                                                    referencePoint, value >= 0.0, text ) );
00256     }
00257 }
00258 
00259 const QFontMetrics* AbstractDiagram::Private::cachedFontMetrics( const QFont& font,
00260                                                                  const QPaintDevice* paintDevice) const
00261 {
00262     if ( ( font != mCachedFont ) || ( paintDevice != mCachedPaintDevice ) ) {
00263         mCachedFontMetrics = QFontMetrics( font, const_cast<QPaintDevice *>( paintDevice ) );
00264         // TODO what about setting mCachedFont and mCachedPaintDevice?
00265     }
00266     return &mCachedFontMetrics;
00267 }
00268 
00269 const QFontMetrics AbstractDiagram::Private::cachedFontMetrics() const
00270 {
00271     return mCachedFontMetrics;
00272 }
00273 
00274 QString AbstractDiagram::Private::formatNumber( qreal value, int decimalDigits ) const
00275 {
00276     const int digits = qMax(decimalDigits, 0);
00277     const qreal roundingEpsilon = pow( 0.1, digits ) * 0.5;
00278     QString asString = QString::number( value + roundingEpsilon, 'f' );
00279     const int decimalPos = asString.indexOf( QLatin1Char( '.' ) );
00280     if ( decimalPos < 0 ) {
00281         return asString;
00282     }
00283 
00284     int last = qMin( decimalPos + digits, asString.length() - 1 );
00285     // remove trailing zeros (and maybe decimal dot)
00286     while ( last > decimalPos && asString[ last ] == QLatin1Char( '0' ) ) {
00287         last--;
00288     }
00289     if ( last == decimalPos ) {
00290          last--;
00291     }
00292     asString.chop( asString.length() - last - 1 );
00293     return asString;
00294 }
00295 
00296 void AbstractDiagram::Private::forgetAlreadyPaintedDataValues()
00297 {
00298     alreadyDrawnDataValueTexts.clear();
00299     prevPaintedDataValueText.clear();
00300 }
00301 
00302 void AbstractDiagram::Private::paintDataValueTextsAndMarkers(
00303     PaintContext* ctx,
00304     const LabelPaintCache &cache,
00305     bool paintMarkers,
00306     bool justCalculateRect /* = false */,
00307     QRectF* cumulatedBoundingRect /* = 0 */ )
00308 {
00309     if ( justCalculateRect && !cumulatedBoundingRect ) {
00310         qWarning() << Q_FUNC_INFO << "Neither painting nor finding the bounding rect, what are we doing?";
00311     }
00312 
00313     const PainterSaver painterSaver( ctx->painter() );
00314     ctx->painter()->setClipping( false );
00315 
00316     if ( paintMarkers && !justCalculateRect ) {
00317         KDAB_FOREACH ( const LabelPaintInfo& info, cache.paintReplay ) {
00318             diagram->paintMarker( ctx->painter(), info.index, info.markerPos );
00319         }
00320     }
00321 
00322     TextAttributes ta;
00323     {
00324         Measure m( 18.0, KDChartEnums::MeasureCalculationModeRelative,
00325                    KDChartEnums::MeasureOrientationMinimum );
00326         m.setReferenceArea( ctx->coordinatePlane() );
00327         ta.setFontSize( m );
00328         m.setAbsoluteValue( 6.0 );
00329         ta.setMinimalFontSize( m );
00330     }
00331 
00332     forgetAlreadyPaintedDataValues();
00333 
00334     KDAB_FOREACH ( const LabelPaintInfo& info, cache.paintReplay ) {
00335         const QPointF pos = info.labelArea.elementAt( 0 );
00336         paintDataValueText( ctx->painter(), info.attrs, pos, info.isValuePositive,
00337                             info.value, justCalculateRect, cumulatedBoundingRect );
00338 
00339         const QString comment = info.index.data( KDChart::CommentRole ).toString();
00340         if ( comment.isEmpty() ) {
00341             continue;
00342         }
00343         TextBubbleLayoutItem item( comment, ta, ctx->coordinatePlane()->parent(),
00344                                    KDChartEnums::MeasureOrientationMinimum,
00345                                    Qt::AlignHCenter | Qt::AlignVCenter );
00346         const QRect rect( pos.toPoint(), item.sizeHint() );
00347 
00348         if (cumulatedBoundingRect) {
00349             (*cumulatedBoundingRect) |= rect;
00350         }
00351         if ( !justCalculateRect ) {
00352             item.setGeometry( rect );
00353             item.paint( ctx->painter() );
00354         }
00355     }
00356 }
00357 
00358 QString AbstractDiagram::Private::formatDataValueText( const DataValueAttributes &dva,
00359                                                        const QModelIndex& index, qreal value ) const
00360 {
00361     if ( !dva.isVisible() ) {
00362         return QString();
00363     }
00364     if ( dva.usePercentage() ) {
00365         value = calcPercentValue( index );
00366     }
00367 
00368     QString ret;
00369     if ( dva.dataLabel().isNull() ) {
00370         ret = formatNumber( value, dva.decimalDigits() );
00371     } else {
00372         ret = dva.dataLabel();
00373     }
00374 
00375     ret.prepend( dva.prefix() );
00376     ret.append( dva.suffix() );
00377 
00378     return ret;
00379 }
00380 
00381 void AbstractDiagram::Private::paintDataValueText(
00382     QPainter* painter,
00383     const QModelIndex& index,
00384     const QPointF& pos,
00385     qreal value,
00386     bool justCalculateRect /* = false */,
00387     QRectF* cumulatedBoundingRect /* = 0 */ )
00388 {
00389     const DataValueAttributes dva( diagram->dataValueAttributes( index ) );
00390     const QString text = formatDataValueText( dva, index, value );
00391     paintDataValueText( painter, dva, pos, value >= 0.0, text,
00392                         justCalculateRect, cumulatedBoundingRect );
00393 }
00394 
00395 void AbstractDiagram::Private::paintDataValueText(
00396     QPainter* painter,
00397     const DataValueAttributes& attrs,
00398     const QPointF& pos,
00399     bool valueIsPositive,
00400     const QString& text,
00401     bool justCalculateRect /* = false */,
00402     QRectF* cumulatedBoundingRect /* = 0 */ )
00403 {
00404     if ( !attrs.isVisible() ) {
00405         return;
00406     }
00407 
00408     const TextAttributes ta( attrs.textAttributes() );
00409     if ( !ta.isVisible() || ( !attrs.showRepetitiveDataLabels() && prevPaintedDataValueText == text ) ) {
00410         return;
00411     }
00412     prevPaintedDataValueText = text;
00413 
00414     QTextDocument doc;
00415     doc.setDocumentMargin( 0.0 );
00416     if ( Qt::mightBeRichText( text ) ) {
00417         doc.setHtml( text );
00418     } else {
00419         doc.setPlainText( text );
00420     }
00421 
00422     const RelativePosition relPos( attrs.position( valueIsPositive ) );
00423     const QFont calculatedFont( ta.calculatedFont( plane, KDChartEnums::MeasureOrientationMinimum ) );
00424 
00425     const PainterSaver painterSaver( painter );
00426     painter->setPen( PrintingParameters::scalePen( ta.pen() ) );
00427 
00428     doc.setDefaultFont( calculatedFont );
00429     QAbstractTextDocumentLayout::PaintContext context;
00430     context.palette = diagram->palette();
00431     context.palette.setColor( QPalette::Text, ta.pen().color() );
00432 
00433     QAbstractTextDocumentLayout* const layout = doc.documentLayout();
00434     layout->setPaintDevice( painter->device() );
00435 
00436     painter->translate( pos.x(), pos.y() );
00437     int rotation = ta.rotation();
00438     if ( !valueIsPositive && attrs.mirrorNegativeValueTextRotation() ) {
00439         rotation *= -1;
00440     }
00441     painter->rotate( rotation );
00442 
00443     // do overlap detection "as seen by the painter"
00444     QTransform transform = painter->worldTransform();
00445 
00446     bool drawIt = true;
00447     // note: This flag can be set differently for every label text!
00448     // In theory a user could e.g. have some small red text on one of the
00449     // values that she wants to have written in any case - so we just
00450     // do not test if such texts would cover some of the others.
00451     if ( !attrs.showOverlappingDataLabels() ) {
00452         const QRectF br( layout->frameBoundingRect( doc.rootFrame() ) );
00453         QPolygon pr = transform.mapToPolygon( br.toRect() );
00454         // Using QPainterPath allows us to use intersects() (which has many early-exits)
00455         // instead of QPolygon::intersected (which calculates a slow and precise intersection polygon)
00456         QPainterPath path;
00457         path.addPolygon( pr );
00458 
00459         // iterate backwards because recently added items are more likely to overlap, so we spend
00460         // less time checking irrelevant items when there is overlap
00461         for ( int i = alreadyDrawnDataValueTexts.count() - 1; i >= 0; i-- ) {
00462             if ( alreadyDrawnDataValueTexts.at( i ).intersects( path ) ) {
00463                 // qDebug() << "not painting this label due to overlap";
00464                 drawIt = false;
00465                 break;
00466             }
00467         }
00468         if ( drawIt ) {
00469             alreadyDrawnDataValueTexts << path;
00470         }
00471     }
00472 
00473     if ( drawIt ) {
00474         QRectF rect = layout->frameBoundingRect( doc.rootFrame() );
00475         if ( cumulatedBoundingRect ) {
00476             (*cumulatedBoundingRect) |= transform.mapRect( rect );
00477         }
00478         if ( !justCalculateRect ) {
00479             bool paintBack = false;
00480             BackgroundAttributes back( attrs.backgroundAttributes() );
00481             if ( back.isVisible() ) {
00482                 paintBack = true;
00483                 painter->setBrush( back.brush() );
00484             } else {
00485                 painter->setBrush( QBrush() );
00486             }
00487 
00488             qreal radius = 0.0;
00489             FrameAttributes frame( attrs.frameAttributes() );
00490             if ( frame.isVisible() ) {
00491                 paintBack = true;
00492                 painter->setPen( frame.pen() );
00493                 radius = frame.cornerRadius();
00494             }
00495 
00496             if ( paintBack ) {
00497                 QRectF borderRect( QPointF( 0, 0 ), rect.size() );
00498                 painter->drawRoundedRect( borderRect, radius, radius );
00499             }
00500             layout->draw( painter, context );
00501         }
00502     }
00503 }
00504 
00505 QModelIndex AbstractDiagram::Private::indexAt( const QPoint& point ) const
00506 {
00507     QModelIndexList l = indexesAt( point );
00508     qSort( l );
00509     if ( !l.isEmpty() )
00510         return l.first();
00511     else
00512         return QModelIndex();
00513 }
00514 
00515 QModelIndexList AbstractDiagram::Private::indexesAt( const QPoint& point ) const
00516 {
00517     return reverseMapper.indexesAt( point ); // which could be empty
00518 }
00519 
00520 QModelIndexList AbstractDiagram::Private::indexesIn( const QRect& rect ) const
00521 {
00522     return reverseMapper.indexesIn( rect );
00523 }
00524 
00525 CartesianDiagramDataCompressor::AggregatedDataValueAttributes AbstractDiagram::Private::aggregatedAttrs(
00526     const QModelIndex& index,
00527     const CartesianDiagramDataCompressor::CachePosition* position ) const
00528 {
00529     Q_UNUSED( position ); // used by cartesian diagrams only
00530     CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs;
00531     allAttrs[index] = diagram->dataValueAttributes( index );
00532     return allAttrs;
00533 }
00534 
00535 void AbstractDiagram::Private::setDatasetAttrs( int dataset, const QVariant& data, int role )
00536 {
00537     // To store attributes for a dataset, we use the first column
00538     // that's associated with it. (i.e., with a dataset dimension
00539     // of two, the column of the keys). In most cases however, there's
00540     // only one data dimension, and thus also only one column per data set.
00541     int column = dataset * datasetDimension;
00542 
00543     // For DataHiddenRole, also store the flag in the other data points that belong to this data set,
00544     // otherwise it's impossible to hide data points in a plotter diagram because there will always
00545     // be one model index that belongs to this data point that is not hidden.
00546     // For more details on how hiding works, see the data compressor.
00547     // Also see KDCH-503 for which this is a workaround.
00548     int columnSpan = role == DataHiddenRole ? datasetDimension : 1;
00549 
00550     for ( int i = 0; i < columnSpan; i++ ) {
00551         attributesModel->setHeaderData( column + i, Qt::Horizontal, data, role );
00552     }
00553 }
00554 
00555 QVariant AbstractDiagram::Private::datasetAttrs( int dataset, int role ) const
00556 {
00557     // See setDataSetAttrs for explanation of column
00558     int column = dataset * datasetDimension;
00559     return attributesModel->headerData( column, Qt::Horizontal, role );
00560 }
00561 
00562 void AbstractDiagram::Private::resetDatasetAttrs( int dataset, int role )
00563 {
00564     // See setDataSetAttrs for explanation of column
00565     int column = dataset * datasetDimension;
00566     attributesModel->resetHeaderData( column, Qt::Horizontal, role );
00567 }
00568 
00569 
00570 LineAttributesInfo::LineAttributesInfo()
00571 {
00572 }
00573 
00574 LineAttributesInfo::LineAttributesInfo( const QModelIndex& _index, const QPointF& _value, const QPointF& _nextValue )
00575     : index( _index )
00576     , value ( _value )
00577     , nextValue ( _nextValue )
00578 {
00579 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Defines

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/