File indexing completed on 2024-05-12 15:54:12

0001 /*
0002  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
0003  *
0004  * This file is part of the KD Chart library.
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU General Public License as
0008  * published by the Free Software Foundation; either version 2 of
0009  * the License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
0018  */
0019 
0020 //
0021 //  W A R N I N G
0022 //  -------------
0023 //
0024 // This file is not part of the KD Chart API.  It exists purely as an
0025 // implementation detail.  This header file may change from version to
0026 // version without notice, or even be removed.
0027 //
0028 // We mean it.
0029 //
0030 
0031 #include "KChartAbstractDiagram_p.h"
0032 
0033 #include "KChartBarDiagram.h"
0034 #include "KChartFrameAttributes.h"
0035 #include "KChartPainterSaver_p.h"
0036 
0037 #include <QAbstractTextDocumentLayout>
0038 #include <QTextBlock>
0039 #include <QApplication>
0040 
0041 
0042 using namespace KChart;
0043 
0044 LabelPaintInfo::LabelPaintInfo() :
0045     isValuePositive( false )
0046 {
0047 }
0048 
0049 LabelPaintInfo::LabelPaintInfo( const QModelIndex& _index, const DataValueAttributes& _attrs,
0050                                 const QPainterPath& _labelArea, const QPointF& _markerPos,
0051                                 bool _isValuePositive, const QString& _value )
0052     : index( _index )
0053     , attrs( _attrs )
0054     , labelArea( _labelArea )
0055     , markerPos( _markerPos )
0056     , isValuePositive( _isValuePositive )
0057     , value( _value )
0058 {
0059 }
0060 
0061 LabelPaintInfo::LabelPaintInfo( const LabelPaintInfo& other )
0062     : index( other.index )
0063     , attrs( other.attrs )
0064     , labelArea( other.labelArea )
0065     , markerPos( other.markerPos )
0066     , isValuePositive( other.isValuePositive )
0067     , value( other.value )
0068 {
0069 }
0070 
0071 AbstractDiagram::Private::Private()
0072   : diagram( nullptr )
0073   , doDumpPaintTime( false )
0074   , plane( nullptr )
0075   , attributesModel( new PrivateAttributesModel(nullptr,nullptr) )
0076   , allowOverlappingDataValueTexts( false )
0077   , antiAliasing( true )
0078   , percent( false )
0079   , datasetDimension( 1 )
0080   , databoundariesDirty( true )
0081   , mCachedFontMetrics( QFontMetrics( qApp->font() ) )
0082 {
0083 }
0084 
0085 AbstractDiagram::Private::~Private()
0086 {
0087   if ( attributesModel && qobject_cast<PrivateAttributesModel*>(attributesModel) )
0088     delete attributesModel;
0089 }
0090 
0091 void AbstractDiagram::Private::init()
0092 {
0093 }
0094 
0095 void AbstractDiagram::Private::init( AbstractCoordinatePlane* newPlane )
0096 {
0097     plane = newPlane;
0098 }
0099 
0100 bool AbstractDiagram::Private::usesExternalAttributesModel() const
0101 {
0102     return ( ! attributesModel.isNull() ) &&
0103            ( ! qobject_cast<PrivateAttributesModel*>(attributesModel) );
0104 }
0105 
0106 void AbstractDiagram::Private::setAttributesModel( AttributesModel* amodel )
0107 {
0108     if ( attributesModel == amodel ) {
0109         return;
0110     }
0111 
0112     if ( !attributesModel.isNull() ) {
0113         if ( qobject_cast< PrivateAttributesModel* >( attributesModel ) ) {
0114             delete attributesModel;
0115         } else {
0116             disconnect( attributesModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
0117                         diagram, SLOT(setDataBoundariesDirty()) );
0118             disconnect( attributesModel, SIGNAL(columnsInserted(QModelIndex,int,int)),
0119                         diagram, SLOT(setDataBoundariesDirty()) );
0120             disconnect( attributesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0121                         diagram, SLOT(setDataBoundariesDirty()) );
0122             disconnect( attributesModel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
0123                         diagram, SLOT(setDataBoundariesDirty()) );
0124             disconnect( attributesModel, SIGNAL(modelReset()),
0125                         diagram, SLOT(setDataBoundariesDirty()) );
0126             disconnect( attributesModel, SIGNAL(layoutChanged()),
0127                         diagram, SLOT(setDataBoundariesDirty()) );
0128             disconnect( attributesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0129                         diagram, SIGNAL(modelDataChanged()));
0130         }
0131     }
0132 
0133     Q_EMIT diagram->attributesModelAboutToChange( amodel, attributesModel );
0134 
0135     connect( amodel, SIGNAL(rowsInserted(QModelIndex,int,int)),
0136              diagram, SLOT(setDataBoundariesDirty()) );
0137     connect( amodel, SIGNAL(columnsInserted(QModelIndex,int,int)),
0138              diagram, SLOT(setDataBoundariesDirty()) );
0139     connect( amodel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0140              diagram, SLOT(setDataBoundariesDirty()) );
0141     connect( amodel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
0142              diagram, SLOT(setDataBoundariesDirty()) );
0143     connect( amodel, SIGNAL(modelReset()),
0144              diagram, SLOT(setDataBoundariesDirty()) );
0145     connect( amodel, SIGNAL(layoutChanged()),
0146              diagram, SLOT(setDataBoundariesDirty()) );
0147     connect( amodel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0148              diagram, SIGNAL(modelDataChanged()));
0149 
0150     attributesModel = amodel;
0151 }
0152 
0153 AbstractDiagram::Private::Private( const AbstractDiagram::Private& rhs ) :
0154     diagram( nullptr ),
0155     doDumpPaintTime( rhs.doDumpPaintTime ),
0156     // Do not copy the plane
0157     plane( nullptr ),
0158     attributesModelRootIndex( QModelIndex() ),
0159     attributesModel( rhs.attributesModel ),
0160     allowOverlappingDataValueTexts( rhs.allowOverlappingDataValueTexts ),
0161     antiAliasing( rhs.antiAliasing ),
0162     percent( rhs.percent ),
0163     datasetDimension( rhs.datasetDimension ),
0164     mCachedFontMetrics( rhs.cachedFontMetrics() )
0165 {
0166     attributesModel = new PrivateAttributesModel( nullptr, nullptr);
0167     attributesModel->initFrom( rhs.attributesModel );
0168 }
0169 
0170 // FIXME: Optimize if necessary
0171 qreal AbstractDiagram::Private::calcPercentValue( const QModelIndex & index ) const
0172 {
0173     qreal sum = 0.0;
0174     for ( int col = 0; col < attributesModel->columnCount( QModelIndex() ); col++ )
0175         sum += attributesModel->data( attributesModel->index( index.row(), col, QModelIndex() ) ).toReal(); // checked
0176     if ( sum == 0.0 )
0177         return 0.0;
0178     return attributesModel->data( attributesModel->mapFromSource( index ) ).toReal() / sum * 100.0;
0179 }
0180 
0181 void AbstractDiagram::Private::addLabel(
0182     LabelPaintCache* cache,
0183     const QModelIndex& index,
0184     const CartesianDiagramDataCompressor::CachePosition* position,
0185     const PositionPoints& points,
0186     const Position& autoPositionPositive, const Position& autoPositionNegative,
0187     const qreal value, qreal favoriteAngle /* = 0.0 */ )
0188 {
0189     CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs(
0190         aggregatedAttrs( index, position ) );
0191 
0192     QMap<QModelIndex, DataValueAttributes>::const_iterator it;
0193     for ( it = allAttrs.constBegin(); it != allAttrs.constEnd(); ++it ) {
0194         DataValueAttributes dva = it.value();
0195         if ( !dva.isVisible() ) {
0196             continue;
0197         }
0198 
0199         const bool isPositive = ( value >= 0.0 );
0200 
0201         RelativePosition relPos( dva.position( isPositive ) );
0202         relPos.setReferencePoints( points );
0203         if ( relPos.referencePosition().isUnknown() ) {
0204             relPos.setReferencePosition( isPositive ? autoPositionPositive : autoPositionNegative );
0205         }
0206 
0207         // Rotate the label position (not the label itself) if the diagram is rotated so that the defaults still work
0208         if ( isTransposed() ) {
0209             KChartEnums::PositionValue posValue = relPos.referencePosition().value();
0210             if ( posValue >= KChartEnums::PositionNorthWest && posValue <= KChartEnums::PositionWest ) {
0211                 // rotate 90 degrees clockwise
0212                 posValue = static_cast< KChartEnums::PositionValue >( posValue + 2 );
0213                 if ( posValue > KChartEnums::PositionWest ) {
0214                     // wraparound
0215                     posValue = static_cast< KChartEnums::PositionValue >( posValue -
0216                                 ( KChartEnums::PositionWest - KChartEnums::PositionNorthWest ) );
0217                 }
0218                 relPos.setReferencePosition( Position( posValue ) );
0219             }
0220         }
0221 
0222         const QPointF referencePoint = relPos.referencePoint();
0223         if ( !diagram->coordinatePlane()->isVisiblePoint( referencePoint ) ) {
0224             continue;
0225         }
0226 
0227         const qreal fontHeight = cachedFontMetrics( dva.textAttributes().
0228                 calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ), diagram )->height();
0229 
0230         // Note: When printing data value texts and padding's Measure is using automatic reference area
0231         //       detection, the font height is used as reference size for both horizontal and vertical
0232         //       padding.
0233         QSizeF relativeMeasureSize( fontHeight, fontHeight );
0234 
0235         if ( !dva.textAttributes().hasRotation() ) {
0236             TextAttributes ta = dva.textAttributes();
0237             ta.setRotation( favoriteAngle );
0238             dva.setTextAttributes( ta );
0239         }
0240 
0241         // get the size of the label text using a subset of the information going into the final layout
0242         const QString text = formatDataValueText( dva, index, value );
0243         QTextDocument doc;
0244         doc.setDocumentMargin( 0 );
0245         if ( Qt::mightBeRichText( text ) ) {
0246             doc.setHtml( text );
0247         } else {
0248             doc.setPlainText( text );
0249         }
0250         const QFont calculatedFont( dva.textAttributes()
0251                                     .calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) );
0252         doc.setDefaultFont( calculatedFont );
0253 
0254         const QRectF plainRect = doc.documentLayout()->frameBoundingRect( doc.rootFrame() );
0255 
0256         /*
0257         * A few hints on how the positioning of the text frame is done:
0258         *
0259         * Let's assume we have a bar chart, a text for a positive value
0260         * to be drawn, and "North" as attrs.positivePosition().
0261         *
0262         * The reference point (pos) is then set to the top center point
0263         * of a bar. The offset now depends on the alignment:
0264         *
0265         *    Top: text is centered horizontally to the bar, bottom of
0266         *         text frame starts at top of bar
0267         *
0268         *    Bottom: text is centered horizontally to the bar, top of
0269         *            text frame starts at top of bar
0270         *
0271         *    Center: text is centered horizontally to the bar, center
0272         *            line of text frame is same as top of bar
0273         *
0274         *    TopLeft: right edge of text frame is horizontal center of
0275         *             bar, bottom of text frame is top of bar.
0276         *
0277         *    ...
0278         *
0279         * Positive and negative value labels are treated equally, "North"
0280         * also refers to the top of a negative bar, and *not* to the bottom.
0281         *
0282         *
0283         * "NorthEast" likewise refers to the top right edge of the bar,
0284         * "NorthWest" to the top left edge of the bar, and so on.
0285         *
0286         * In other words, attrs.positivePosition() always refers to a
0287         * position of the *bar*, and relPos.alignment() always refers
0288         * to an alignment of the text frame relative to this position.
0289         */
0290 
0291         QTransform transform;
0292         {
0293             // move to the general area where the label should be
0294             QPointF calcPoint = relPos.calculatedPoint( relativeMeasureSize );
0295             transform.translate( calcPoint.x(), calcPoint.y() );
0296             // align the text rect; find out by how many half-widths / half-heights to move.
0297             int dx = -1;
0298             if ( relPos.alignment() & Qt::AlignLeft ) {
0299                 dx -= 1;
0300             } else if ( relPos.alignment() & Qt::AlignRight ) {
0301                  dx += 1;
0302             }
0303 
0304             int dy = -1;
0305             if ( relPos.alignment() & Qt::AlignTop ) {
0306                 dy -= 1;
0307             } else if ( relPos.alignment() & Qt::AlignBottom ) {
0308                 dy += 1;
0309             }
0310             transform.translate( qreal( dx ) * plainRect.width() * 0.5,
0311                                  qreal( dy ) * plainRect.height() * 0.5 );
0312 
0313             // rotate the text rect around its center
0314             transform.translate( plainRect.center().x(), plainRect.center().y() );
0315             int rotation = dva.textAttributes().rotation();
0316             if ( !isPositive && dva.mirrorNegativeValueTextRotation() ) {
0317                 rotation *= -1;
0318             }
0319             transform.rotate( rotation );
0320             transform.translate( -plainRect.center().x(), -plainRect.center().y() );
0321         }
0322 
0323         QPainterPath labelArea;
0324         //labelArea.addPolygon( transform.mapToPolygon( plainRect.toRect() ) );
0325         //labelArea.closeSubpath();
0326         // Not doing that because QTransform has a special case for 180° that gives a different than
0327         // usual ordering of the points in the polygon returned by mapToPolygon( const QRect & ).
0328         // We expect a particular ordering in paintDataValueTextsAndMarkers() by using elementAt( 0 ),
0329         // and similar things might happen elsewhere.
0330         labelArea.addPolygon( transform.map( QPolygon( plainRect.toRect(), true ) ) );
0331 
0332         // store the label geometry and auxiliary data
0333         cache->paintReplay.append( LabelPaintInfo( it.key(), dva, labelArea,
0334                                                    referencePoint, value >= 0.0, text ) );
0335     }
0336 }
0337 
0338 const QFontMetrics* AbstractDiagram::Private::cachedFontMetrics( const QFont& font,
0339                                                                  const QPaintDevice* paintDevice) const
0340 {
0341     if ( ( font != mCachedFont ) || ( paintDevice != mCachedPaintDevice ) ) {
0342         mCachedFontMetrics = QFontMetrics( font, const_cast<QPaintDevice *>( paintDevice ) );
0343         // TODO what about setting mCachedFont and mCachedPaintDevice?
0344     }
0345     return &mCachedFontMetrics;
0346 }
0347 
0348 const QFontMetrics AbstractDiagram::Private::cachedFontMetrics() const
0349 {
0350     return mCachedFontMetrics;
0351 }
0352 
0353 QString AbstractDiagram::Private::formatNumber( qreal value, int decimalDigits ) const
0354 {
0355     const int digits = qMax(decimalDigits, 0);
0356     const qreal roundingEpsilon = pow( 0.1, digits ) * ( value >= 0.0 ? 0.5 : -0.5 );
0357     QString asString = QString::number( value + roundingEpsilon, 'f' );
0358     const int decimalPos = asString.indexOf( QLatin1Char( '.' ) );
0359     if ( decimalPos < 0 ) {
0360         return asString;
0361     }
0362 
0363     int last = qMin( decimalPos + digits, asString.length() - 1 );
0364     // remove trailing zeros (and maybe decimal dot)
0365     while ( last > decimalPos && asString[ last ] == QLatin1Char( '0' ) ) {
0366         last--;
0367     }
0368     if ( last == decimalPos ) {
0369          last--;
0370     }
0371     asString.chop( asString.length() - last - 1 );
0372     return asString;
0373 }
0374 
0375 void AbstractDiagram::Private::forgetAlreadyPaintedDataValues()
0376 {
0377     alreadyDrawnDataValueTexts.clear();
0378     prevPaintedDataValueText.clear();
0379 }
0380 
0381 void AbstractDiagram::Private::paintDataValueTextsAndMarkers(
0382     PaintContext* ctx,
0383     const LabelPaintCache &cache,
0384     bool paintMarkers,
0385     bool justCalculateRect /* = false */,
0386     QRectF* cumulatedBoundingRect /* = 0 */ )
0387 {
0388     if ( justCalculateRect && !cumulatedBoundingRect ) {
0389         qWarning() << Q_FUNC_INFO << "Neither painting nor finding the bounding rect, what are we doing?";
0390     }
0391 
0392     const PainterSaver painterSaver( ctx->painter() );
0393     ctx->painter()->setClipping( false );
0394 
0395     if ( paintMarkers && !justCalculateRect ) {
0396         for ( const LabelPaintInfo& info : qAsConst(cache.paintReplay) ) {
0397             diagram->paintMarker( ctx->painter(), info.index, info.markerPos );
0398         }
0399     }
0400 
0401     TextAttributes ta;
0402     {
0403         Measure m( 18.0, KChartEnums::MeasureCalculationModeRelative,
0404                    KChartEnums::MeasureOrientationMinimum );
0405         m.setReferenceArea( ctx->coordinatePlane() );
0406         ta.setFontSize( m );
0407         m.setAbsoluteValue( 6.0 );
0408         ta.setMinimalFontSize( m );
0409     }
0410 
0411     forgetAlreadyPaintedDataValues();
0412 
0413     for ( const LabelPaintInfo& info : qAsConst(cache.paintReplay) ) {
0414         const QPointF pos = info.labelArea.elementAt( 0 );
0415         paintDataValueText( ctx->painter(), info.attrs, pos, info.isValuePositive,
0416                             info.value, justCalculateRect, cumulatedBoundingRect );
0417 
0418         const QString comment = info.index.data( KChart::CommentRole ).toString();
0419         if ( comment.isEmpty() ) {
0420             continue;
0421         }
0422         TextBubbleLayoutItem item( comment, ta, ctx->coordinatePlane()->parent(),
0423                                    KChartEnums::MeasureOrientationMinimum,
0424                                    Qt::AlignHCenter | Qt::AlignVCenter );
0425         const QRect rect( pos.toPoint(), item.sizeHint() );
0426 
0427         if (cumulatedBoundingRect) {
0428             (*cumulatedBoundingRect) |= rect;
0429         }
0430         if ( !justCalculateRect ) {
0431             item.setGeometry( rect );
0432             item.paint( ctx->painter() );
0433         }
0434     }
0435     if ( cumulatedBoundingRect ) {
0436         *cumulatedBoundingRect = ctx->painter()->transform().inverted().mapRect( *cumulatedBoundingRect );
0437     }
0438 }
0439 
0440 QString AbstractDiagram::Private::formatDataValueText( const DataValueAttributes &dva,
0441                                                        const QModelIndex& index, qreal value ) const
0442 {
0443     if ( !dva.isVisible() ) {
0444         return QString();
0445     }
0446     if ( dva.usePercentage() ) {
0447         value = calcPercentValue( index );
0448     }
0449 
0450     QString ret;
0451     if ( dva.dataLabel().isNull() ) {
0452         ret = formatNumber( value, dva.decimalDigits() );
0453     } else {
0454         ret = dva.dataLabel();
0455     }
0456 
0457     ret.prepend( dva.prefix() );
0458     ret.append( dva.suffix() );
0459 
0460     return ret;
0461 }
0462 
0463 void AbstractDiagram::Private::paintDataValueText(
0464     QPainter* painter,
0465     const QModelIndex& index,
0466     const QPointF& pos,
0467     qreal value,
0468     bool justCalculateRect /* = false */,
0469     QRectF* cumulatedBoundingRect /* = 0 */ )
0470 {
0471     const DataValueAttributes dva( diagram->dataValueAttributes( index ) );
0472     const QString text = formatDataValueText( dva, index, value );
0473     paintDataValueText( painter, dva, pos, value >= 0.0, text,
0474                         justCalculateRect, cumulatedBoundingRect );
0475 }
0476 
0477 void AbstractDiagram::Private::paintDataValueText(
0478     QPainter* painter,
0479     const DataValueAttributes& attrs,
0480     const QPointF& pos,
0481     bool valueIsPositive,
0482     const QString& text,
0483     bool justCalculateRect /* = false */,
0484     QRectF* cumulatedBoundingRect /* = 0 */ )
0485 {
0486     if ( !attrs.isVisible() ) {
0487         return;
0488     }
0489 
0490     const TextAttributes ta( attrs.textAttributes() );
0491     if ( !ta.isVisible() || ( !attrs.showRepetitiveDataLabels() && prevPaintedDataValueText == text ) ) {
0492         return;
0493     }
0494     prevPaintedDataValueText = text;
0495 
0496     QTextDocument doc;
0497     doc.setDocumentMargin( 0.0 );
0498     if ( Qt::mightBeRichText( text ) ) {
0499         doc.setHtml( text );
0500     } else {
0501         doc.setPlainText( text );
0502     }
0503 
0504     const QFont calculatedFont( ta.calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) );
0505 
0506     const PainterSaver painterSaver( painter );
0507     painter->setPen( PrintingParameters::scalePen( ta.pen() ) );
0508 
0509     doc.setDefaultFont( calculatedFont );
0510     QAbstractTextDocumentLayout::PaintContext context;
0511     context.palette = diagram->palette();
0512     context.palette.setColor( QPalette::Text, ta.pen().color() );
0513 
0514     QAbstractTextDocumentLayout* const layout = doc.documentLayout();
0515     layout->setPaintDevice( painter->device() );
0516 
0517     painter->translate( pos.x(), pos.y() );
0518     int rotation = ta.rotation();
0519     if ( !valueIsPositive && attrs.mirrorNegativeValueTextRotation() ) {
0520         rotation *= -1;
0521     }
0522     painter->rotate( rotation );
0523 
0524     // do overlap detection "as seen by the painter"
0525     QTransform transform = painter->worldTransform();
0526 
0527     bool drawIt = true;
0528     // note: This flag can be set differently for every label text!
0529     // In theory a user could e.g. have some small red text on one of the
0530     // values that she wants to have written in any case - so we just
0531     // do not test if such texts would cover some of the others.
0532     if ( !attrs.showOverlappingDataLabels() ) {
0533         const QRectF br( layout->frameBoundingRect( doc.rootFrame() ) );
0534         QPolygon pr = transform.mapToPolygon( br.toRect() );
0535         // Using QPainterPath allows us to use intersects() (which has many early-exits)
0536         // instead of QPolygon::intersected (which calculates a slow and precise intersection polygon)
0537         QPainterPath path;
0538         path.addPolygon( pr );
0539 
0540         // iterate backwards because recently added items are more likely to overlap, so we spend
0541         // less time checking irrelevant items when there is overlap
0542         for ( int i = alreadyDrawnDataValueTexts.count() - 1; i >= 0; i-- ) {
0543             if ( alreadyDrawnDataValueTexts.at( i ).intersects( path ) ) {
0544                 // qDebug() << "not painting this label due to overlap";
0545                 drawIt = false;
0546                 break;
0547             }
0548         }
0549         if ( drawIt ) {
0550             alreadyDrawnDataValueTexts << path;
0551         }
0552     }
0553 
0554     if ( drawIt ) {
0555         QRectF rect = layout->frameBoundingRect( doc.rootFrame() );
0556         if ( cumulatedBoundingRect ) {
0557             (*cumulatedBoundingRect) |= transform.mapRect( rect );
0558         }
0559         if ( !justCalculateRect ) {
0560             bool paintBack = false;
0561             BackgroundAttributes back( attrs.backgroundAttributes() );
0562             if ( back.isVisible() ) {
0563                 paintBack = true;
0564                 painter->setBrush( back.brush() );
0565             } else {
0566                 painter->setBrush( QBrush() );
0567             }
0568 
0569             qreal radius = 0.0;
0570             FrameAttributes frame( attrs.frameAttributes() );
0571             if ( frame.isVisible() ) {
0572                 paintBack = true;
0573                 painter->setPen( frame.pen() );
0574                 radius = frame.cornerRadius();
0575             }
0576 
0577             if ( paintBack ) {
0578                 QRectF borderRect( QPointF( 0, 0 ), rect.size() );
0579                 painter->drawRoundedRect( borderRect, radius, radius );
0580             }
0581             layout->draw( painter, context );
0582         }
0583     }
0584 }
0585 
0586 QModelIndex AbstractDiagram::Private::indexAt( const QPoint& point ) const
0587 {
0588     QModelIndexList l = indexesAt( point );
0589     std::sort(l.begin(), l.end());
0590     if ( !l.isEmpty() )
0591         return l.first();
0592     else
0593         return QModelIndex();
0594 }
0595 
0596 QModelIndexList AbstractDiagram::Private::indexesAt( const QPoint& point ) const
0597 {
0598     return reverseMapper.indexesAt( point ); // which could be empty
0599 }
0600 
0601 QModelIndexList AbstractDiagram::Private::indexesIn( const QRect& rect ) const
0602 {
0603     return reverseMapper.indexesIn( rect );
0604 }
0605 
0606 CartesianDiagramDataCompressor::AggregatedDataValueAttributes AbstractDiagram::Private::aggregatedAttrs(
0607     const QModelIndex& index,
0608     const CartesianDiagramDataCompressor::CachePosition* position ) const
0609 {
0610     Q_UNUSED( position ); // used by cartesian diagrams only
0611     CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs;
0612     allAttrs[index] = diagram->dataValueAttributes( index );
0613     return allAttrs;
0614 }
0615 
0616 void AbstractDiagram::Private::setDatasetAttrs( int dataset, const QVariant& data, int role )
0617 {
0618     // To store attributes for a dataset, we use the first column
0619     // that's associated with it. (i.e., with a dataset dimension
0620     // of two, the column of the keys). In most cases however, there's
0621     // only one data dimension, and thus also only one column per data set.
0622     int column = dataset * datasetDimension;
0623 
0624     // For DataHiddenRole, also store the flag in the other data points that belong to this data set,
0625     // otherwise it's impossible to hide data points in a plotter diagram because there will always
0626     // be one model index that belongs to this data point that is not hidden.
0627     // For more details on how hiding works, see the data compressor.
0628     // Also see KDCH-503 for which this is a workaround.
0629     int columnSpan = role == DataHiddenRole ? datasetDimension : 1;
0630 
0631     for ( int i = 0; i < columnSpan; i++ ) {
0632         attributesModel->setHeaderData( column + i, Qt::Horizontal, data, role );
0633     }
0634 }
0635 
0636 QVariant AbstractDiagram::Private::datasetAttrs( int dataset, int role ) const
0637 {
0638     // See setDataSetAttrs for explanation of column
0639     int column = dataset * datasetDimension;
0640     return attributesModel->headerData( column, Qt::Horizontal, role );
0641 }
0642 
0643 void AbstractDiagram::Private::resetDatasetAttrs( int dataset, int role )
0644 {
0645     // See setDataSetAttrs for explanation of column
0646     int column = dataset * datasetDimension;
0647     attributesModel->resetHeaderData( column, Qt::Horizontal, role );
0648 }
0649 
0650 bool AbstractDiagram::Private::isTransposed() const
0651 {
0652      // Determine the diagram that specifies the orientation.
0653      // That diagram is the reference diagram, if it exists, or otherwise the diagram itself.
0654      // Note: In KChart 2.3 or earlier, only a bar diagram can be transposed.
0655      const AbstractCartesianDiagram* refDiagram = qobject_cast< const AbstractCartesianDiagram * >( diagram );
0656      if ( !refDiagram ) {
0657          return false;
0658      }
0659      if ( refDiagram->referenceDiagram() ) {
0660          refDiagram = refDiagram->referenceDiagram();
0661      }
0662      const BarDiagram* barDiagram = qobject_cast< const BarDiagram* >( refDiagram );
0663      if ( !barDiagram ) {
0664          return false;
0665      }
0666      return barDiagram->orientation() == Qt::Horizontal;
0667 }
0668 
0669 LineAttributesInfo::LineAttributesInfo()
0670 {
0671 }
0672 
0673 LineAttributesInfo::LineAttributesInfo( const QModelIndex& _index, const QPointF& _value, const QPointF& _nextValue )
0674     : index( _index )
0675     , value ( _value )
0676     , nextValue ( _nextValue )
0677 {
0678 }