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 }