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 }