File indexing completed on 2024-05-12 04:20:30

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