1 /*
2 * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3 *
4 * This file is part of the KD Chart library.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 //
21 // W A R N I N G
22 // -------------
23 //
24 // This file is not part of the KD Chart API. It exists purely as an
25 // implementation detail. This header file may change from version to
26 // version without notice, or even be removed.
27 //
28 // We mean it.
29 //
30
31 #include "KChartAbstractDiagram_p.h"
32
33 #include "KChartBarDiagram.h"
34 #include "KChartFrameAttributes.h"
35 #include "KChartPainterSaver_p.h"
36
37 #include <QAbstractTextDocumentLayout>
38 #include <QTextBlock>
39 #include <QApplication>
40
41
42 using namespace KChart;
43
LabelPaintInfo()44 LabelPaintInfo::LabelPaintInfo() :
45 isValuePositive( false )
46 {
47 }
48
LabelPaintInfo(const QModelIndex & _index,const DataValueAttributes & _attrs,const QPainterPath & _labelArea,const QPointF & _markerPos,bool _isValuePositive,const QString & _value)49 LabelPaintInfo::LabelPaintInfo( const QModelIndex& _index, const DataValueAttributes& _attrs,
50 const QPainterPath& _labelArea, const QPointF& _markerPos,
51 bool _isValuePositive, const QString& _value )
52 : index( _index )
53 , attrs( _attrs )
54 , labelArea( _labelArea )
55 , markerPos( _markerPos )
56 , isValuePositive( _isValuePositive )
57 , value( _value )
58 {
59 }
60
LabelPaintInfo(const LabelPaintInfo & other)61 LabelPaintInfo::LabelPaintInfo( const LabelPaintInfo& other )
62 : index( other.index )
63 , attrs( other.attrs )
64 , labelArea( other.labelArea )
65 , markerPos( other.markerPos )
66 , isValuePositive( other.isValuePositive )
67 , value( other.value )
68 {
69 }
70
Private()71 AbstractDiagram::Private::Private()
72 : diagram( nullptr )
73 , doDumpPaintTime( false )
74 , plane( nullptr )
75 , attributesModel( new PrivateAttributesModel(nullptr,nullptr) )
76 , allowOverlappingDataValueTexts( false )
77 , antiAliasing( true )
78 , percent( false )
79 , datasetDimension( 1 )
80 , databoundariesDirty( true )
81 , mCachedFontMetrics( QFontMetrics( qApp->font() ) )
82 {
83 }
84
~Private()85 AbstractDiagram::Private::~Private()
86 {
87 if ( attributesModel && qobject_cast<PrivateAttributesModel*>(attributesModel) )
88 delete attributesModel;
89 }
90
init()91 void AbstractDiagram::Private::init()
92 {
93 }
94
init(AbstractCoordinatePlane * newPlane)95 void AbstractDiagram::Private::init( AbstractCoordinatePlane* newPlane )
96 {
97 plane = newPlane;
98 }
99
usesExternalAttributesModel() const100 bool AbstractDiagram::Private::usesExternalAttributesModel() const
101 {
102 return ( ! attributesModel.isNull() ) &&
103 ( ! qobject_cast<PrivateAttributesModel*>(attributesModel) );
104 }
105
setAttributesModel(AttributesModel * amodel)106 void AbstractDiagram::Private::setAttributesModel( AttributesModel* amodel )
107 {
108 if ( attributesModel == amodel ) {
109 return;
110 }
111
112 if ( !attributesModel.isNull() ) {
113 if ( qobject_cast< PrivateAttributesModel* >( attributesModel ) ) {
114 delete attributesModel;
115 } else {
116 disconnect( attributesModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
117 diagram, SLOT(setDataBoundariesDirty()) );
118 disconnect( attributesModel, SIGNAL(columnsInserted(QModelIndex,int,int)),
119 diagram, SLOT(setDataBoundariesDirty()) );
120 disconnect( attributesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
121 diagram, SLOT(setDataBoundariesDirty()) );
122 disconnect( attributesModel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
123 diagram, SLOT(setDataBoundariesDirty()) );
124 disconnect( attributesModel, SIGNAL(modelReset()),
125 diagram, SLOT(setDataBoundariesDirty()) );
126 disconnect( attributesModel, SIGNAL(layoutChanged()),
127 diagram, SLOT(setDataBoundariesDirty()) );
128 disconnect( attributesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
129 diagram, SIGNAL(modelDataChanged()));
130 }
131 }
132
133 Q_EMIT diagram->attributesModelAboutToChange( amodel, attributesModel );
134
135 connect( amodel, SIGNAL(rowsInserted(QModelIndex,int,int)),
136 diagram, SLOT(setDataBoundariesDirty()) );
137 connect( amodel, SIGNAL(columnsInserted(QModelIndex,int,int)),
138 diagram, SLOT(setDataBoundariesDirty()) );
139 connect( amodel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
140 diagram, SLOT(setDataBoundariesDirty()) );
141 connect( amodel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
142 diagram, SLOT(setDataBoundariesDirty()) );
143 connect( amodel, SIGNAL(modelReset()),
144 diagram, SLOT(setDataBoundariesDirty()) );
145 connect( amodel, SIGNAL(layoutChanged()),
146 diagram, SLOT(setDataBoundariesDirty()) );
147 connect( amodel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
148 diagram, SIGNAL(modelDataChanged()));
149
150 attributesModel = amodel;
151 }
152
Private(const AbstractDiagram::Private & rhs)153 AbstractDiagram::Private::Private( const AbstractDiagram::Private& rhs ) :
154 diagram( nullptr ),
155 doDumpPaintTime( rhs.doDumpPaintTime ),
156 // Do not copy the plane
157 plane( nullptr ),
158 attributesModelRootIndex( QModelIndex() ),
159 attributesModel( rhs.attributesModel ),
160 allowOverlappingDataValueTexts( rhs.allowOverlappingDataValueTexts ),
161 antiAliasing( rhs.antiAliasing ),
162 percent( rhs.percent ),
163 datasetDimension( rhs.datasetDimension ),
164 mCachedFontMetrics( rhs.cachedFontMetrics() )
165 {
166 attributesModel = new PrivateAttributesModel( nullptr, nullptr);
167 attributesModel->initFrom( rhs.attributesModel );
168 }
169
170 // FIXME: Optimize if necessary
calcPercentValue(const QModelIndex & index) const171 qreal AbstractDiagram::Private::calcPercentValue( const QModelIndex & index ) const
172 {
173 qreal sum = 0.0;
174 for ( int col = 0; col < attributesModel->columnCount( QModelIndex() ); col++ )
175 sum += attributesModel->data( attributesModel->index( index.row(), col, QModelIndex() ) ).toReal(); // checked
176 if ( sum == 0.0 )
177 return 0.0;
178 return attributesModel->data( attributesModel->mapFromSource( index ) ).toReal() / sum * 100.0;
179 }
180
addLabel(LabelPaintCache * cache,const QModelIndex & index,const CartesianDiagramDataCompressor::CachePosition * position,const PositionPoints & points,const Position & autoPositionPositive,const Position & autoPositionNegative,const qreal value,qreal favoriteAngle)181 void AbstractDiagram::Private::addLabel(
182 LabelPaintCache* cache,
183 const QModelIndex& index,
184 const CartesianDiagramDataCompressor::CachePosition* position,
185 const PositionPoints& points,
186 const Position& autoPositionPositive, const Position& autoPositionNegative,
187 const qreal value, qreal favoriteAngle /* = 0.0 */ )
188 {
189 CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs(
190 aggregatedAttrs( index, position ) );
191
192 QMap<QModelIndex, DataValueAttributes>::const_iterator it;
193 for ( it = allAttrs.constBegin(); it != allAttrs.constEnd(); ++it ) {
194 DataValueAttributes dva = it.value();
195 if ( !dva.isVisible() ) {
196 continue;
197 }
198
199 const bool isPositive = ( value >= 0.0 );
200
201 RelativePosition relPos( dva.position( isPositive ) );
202 relPos.setReferencePoints( points );
203 if ( relPos.referencePosition().isUnknown() ) {
204 relPos.setReferencePosition( isPositive ? autoPositionPositive : autoPositionNegative );
205 }
206
207 // Rotate the label position (not the label itself) if the diagram is rotated so that the defaults still work
208 if ( isTransposed() ) {
209 KChartEnums::PositionValue posValue = relPos.referencePosition().value();
210 if ( posValue >= KChartEnums::PositionNorthWest && posValue <= KChartEnums::PositionWest ) {
211 // rotate 90 degrees clockwise
212 posValue = static_cast< KChartEnums::PositionValue >( posValue + 2 );
213 if ( posValue > KChartEnums::PositionWest ) {
214 // wraparound
215 posValue = static_cast< KChartEnums::PositionValue >( posValue -
216 ( KChartEnums::PositionWest - KChartEnums::PositionNorthWest ) );
217 }
218 relPos.setReferencePosition( Position( posValue ) );
219 }
220 }
221
222 const QPointF referencePoint = relPos.referencePoint();
223 if ( !diagram->coordinatePlane()->isVisiblePoint( referencePoint ) ) {
224 continue;
225 }
226
227 const qreal fontHeight = cachedFontMetrics( dva.textAttributes().
228 calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ), diagram )->height();
229
230 // Note: When printing data value texts and padding's Measure is using automatic reference area
231 // detection, the font height is used as reference size for both horizontal and vertical
232 // padding.
233 QSizeF relativeMeasureSize( fontHeight, fontHeight );
234
235 if ( !dva.textAttributes().hasRotation() ) {
236 TextAttributes ta = dva.textAttributes();
237 ta.setRotation( favoriteAngle );
238 dva.setTextAttributes( ta );
239 }
240
241 // get the size of the label text using a subset of the information going into the final layout
242 const QString text = formatDataValueText( dva, index, value );
243 QTextDocument doc;
244 doc.setDocumentMargin( 0 );
245 if ( Qt::mightBeRichText( text ) ) {
246 doc.setHtml( text );
247 } else {
248 doc.setPlainText( text );
249 }
250 const QFont calculatedFont( dva.textAttributes()
251 .calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) );
252 doc.setDefaultFont( calculatedFont );
253
254 const QRectF plainRect = doc.documentLayout()->frameBoundingRect( doc.rootFrame() );
255
256 /*
257 * A few hints on how the positioning of the text frame is done:
258 *
259 * Let's assume we have a bar chart, a text for a positive value
260 * to be drawn, and "North" as attrs.positivePosition().
261 *
262 * The reference point (pos) is then set to the top center point
263 * of a bar. The offset now depends on the alignment:
264 *
265 * Top: text is centered horizontally to the bar, bottom of
266 * text frame starts at top of bar
267 *
268 * Bottom: text is centered horizontally to the bar, top of
269 * text frame starts at top of bar
270 *
271 * Center: text is centered horizontally to the bar, center
272 * line of text frame is same as top of bar
273 *
274 * TopLeft: right edge of text frame is horizontal center of
275 * bar, bottom of text frame is top of bar.
276 *
277 * ...
278 *
279 * Positive and negative value labels are treated equally, "North"
280 * also refers to the top of a negative bar, and *not* to the bottom.
281 *
282 *
283 * "NorthEast" likewise refers to the top right edge of the bar,
284 * "NorthWest" to the top left edge of the bar, and so on.
285 *
286 * In other words, attrs.positivePosition() always refers to a
287 * position of the *bar*, and relPos.alignment() always refers
288 * to an alignment of the text frame relative to this position.
289 */
290
291 QTransform transform;
292 {
293 // move to the general area where the label should be
294 QPointF calcPoint = relPos.calculatedPoint( relativeMeasureSize );
295 transform.translate( calcPoint.x(), calcPoint.y() );
296 // align the text rect; find out by how many half-widths / half-heights to move.
297 int dx = -1;
298 if ( relPos.alignment() & Qt::AlignLeft ) {
299 dx -= 1;
300 } else if ( relPos.alignment() & Qt::AlignRight ) {
301 dx += 1;
302 }
303
304 int dy = -1;
305 if ( relPos.alignment() & Qt::AlignTop ) {
306 dy -= 1;
307 } else if ( relPos.alignment() & Qt::AlignBottom ) {
308 dy += 1;
309 }
310 transform.translate( qreal( dx ) * plainRect.width() * 0.5,
311 qreal( dy ) * plainRect.height() * 0.5 );
312
313 // rotate the text rect around its center
314 transform.translate( plainRect.center().x(), plainRect.center().y() );
315 int rotation = dva.textAttributes().rotation();
316 if ( !isPositive && dva.mirrorNegativeValueTextRotation() ) {
317 rotation *= -1;
318 }
319 transform.rotate( rotation );
320 transform.translate( -plainRect.center().x(), -plainRect.center().y() );
321 }
322
323 QPainterPath labelArea;
324 //labelArea.addPolygon( transform.mapToPolygon( plainRect.toRect() ) );
325 //labelArea.closeSubpath();
326 // Not doing that because QTransform has a special case for 180° that gives a different than
327 // usual ordering of the points in the polygon returned by mapToPolygon( const QRect & ).
328 // We expect a particular ordering in paintDataValueTextsAndMarkers() by using elementAt( 0 ),
329 // and similar things might happen elsewhere.
330 labelArea.addPolygon( transform.map( QPolygon( plainRect.toRect(), true ) ) );
331
332 // store the label geometry and auxiliary data
333 cache->paintReplay.append( LabelPaintInfo( it.key(), dva, labelArea,
334 referencePoint, value >= 0.0, text ) );
335 }
336 }
337
cachedFontMetrics(const QFont & font,const QPaintDevice * paintDevice) const338 const QFontMetrics* AbstractDiagram::Private::cachedFontMetrics( const QFont& font,
339 const QPaintDevice* paintDevice) const
340 {
341 if ( ( font != mCachedFont ) || ( paintDevice != mCachedPaintDevice ) ) {
342 mCachedFontMetrics = QFontMetrics( font, const_cast<QPaintDevice *>( paintDevice ) );
343 // TODO what about setting mCachedFont and mCachedPaintDevice?
344 }
345 return &mCachedFontMetrics;
346 }
347
cachedFontMetrics() const348 const QFontMetrics AbstractDiagram::Private::cachedFontMetrics() const
349 {
350 return mCachedFontMetrics;
351 }
352
formatNumber(qreal value,int decimalDigits) const353 QString AbstractDiagram::Private::formatNumber( qreal value, int decimalDigits ) const
354 {
355 const int digits = qMax(decimalDigits, 0);
356 const qreal roundingEpsilon = pow( 0.1, digits ) * ( value >= 0.0 ? 0.5 : -0.5 );
357 QString asString = QString::number( value + roundingEpsilon, 'f' );
358 const int decimalPos = asString.indexOf( QLatin1Char( '.' ) );
359 if ( decimalPos < 0 ) {
360 return asString;
361 }
362
363 int last = qMin( decimalPos + digits, asString.length() - 1 );
364 // remove trailing zeros (and maybe decimal dot)
365 while ( last > decimalPos && asString[ last ] == QLatin1Char( '0' ) ) {
366 last--;
367 }
368 if ( last == decimalPos ) {
369 last--;
370 }
371 asString.chop( asString.length() - last - 1 );
372 return asString;
373 }
374
forgetAlreadyPaintedDataValues()375 void AbstractDiagram::Private::forgetAlreadyPaintedDataValues()
376 {
377 alreadyDrawnDataValueTexts.clear();
378 prevPaintedDataValueText.clear();
379 }
380
paintDataValueTextsAndMarkers(PaintContext * ctx,const LabelPaintCache & cache,bool paintMarkers,bool justCalculateRect,QRectF * cumulatedBoundingRect)381 void AbstractDiagram::Private::paintDataValueTextsAndMarkers(
382 PaintContext* ctx,
383 const LabelPaintCache &cache,
384 bool paintMarkers,
385 bool justCalculateRect /* = false */,
386 QRectF* cumulatedBoundingRect /* = 0 */ )
387 {
388 if ( justCalculateRect && !cumulatedBoundingRect ) {
389 qWarning() << Q_FUNC_INFO << "Neither painting nor finding the bounding rect, what are we doing?";
390 }
391
392 const PainterSaver painterSaver( ctx->painter() );
393 ctx->painter()->setClipping( false );
394
395 if ( paintMarkers && !justCalculateRect ) {
396 for ( const LabelPaintInfo& info : qAsConst(cache.paintReplay) ) {
397 diagram->paintMarker( ctx->painter(), info.index, info.markerPos );
398 }
399 }
400
401 TextAttributes ta;
402 {
403 Measure m( 18.0, KChartEnums::MeasureCalculationModeRelative,
404 KChartEnums::MeasureOrientationMinimum );
405 m.setReferenceArea( ctx->coordinatePlane() );
406 ta.setFontSize( m );
407 m.setAbsoluteValue( 6.0 );
408 ta.setMinimalFontSize( m );
409 }
410
411 forgetAlreadyPaintedDataValues();
412
413 for ( const LabelPaintInfo& info : qAsConst(cache.paintReplay) ) {
414 const QPointF pos = info.labelArea.elementAt( 0 );
415 paintDataValueText( ctx->painter(), info.attrs, pos, info.isValuePositive,
416 info.value, justCalculateRect, cumulatedBoundingRect );
417
418 const QString comment = info.index.data( KChart::CommentRole ).toString();
419 if ( comment.isEmpty() ) {
420 continue;
421 }
422 TextBubbleLayoutItem item( comment, ta, ctx->coordinatePlane()->parent(),
423 KChartEnums::MeasureOrientationMinimum,
424 Qt::AlignHCenter | Qt::AlignVCenter );
425 const QRect rect( pos.toPoint(), item.sizeHint() );
426
427 if (cumulatedBoundingRect) {
428 (*cumulatedBoundingRect) |= rect;
429 }
430 if ( !justCalculateRect ) {
431 item.setGeometry( rect );
432 item.paint( ctx->painter() );
433 }
434 }
435 if ( cumulatedBoundingRect ) {
436 *cumulatedBoundingRect = ctx->painter()->transform().inverted().mapRect( *cumulatedBoundingRect );
437 }
438 }
439
formatDataValueText(const DataValueAttributes & dva,const QModelIndex & index,qreal value) const440 QString AbstractDiagram::Private::formatDataValueText( const DataValueAttributes &dva,
441 const QModelIndex& index, qreal value ) const
442 {
443 if ( !dva.isVisible() ) {
444 return QString();
445 }
446 if ( dva.usePercentage() ) {
447 value = calcPercentValue( index );
448 }
449
450 QString ret;
451 if ( dva.dataLabel().isNull() ) {
452 ret = formatNumber( value, dva.decimalDigits() );
453 } else {
454 ret = dva.dataLabel();
455 }
456
457 ret.prepend( dva.prefix() );
458 ret.append( dva.suffix() );
459
460 return ret;
461 }
462
paintDataValueText(QPainter * painter,const QModelIndex & index,const QPointF & pos,qreal value,bool justCalculateRect,QRectF * cumulatedBoundingRect)463 void AbstractDiagram::Private::paintDataValueText(
464 QPainter* painter,
465 const QModelIndex& index,
466 const QPointF& pos,
467 qreal value,
468 bool justCalculateRect /* = false */,
469 QRectF* cumulatedBoundingRect /* = 0 */ )
470 {
471 const DataValueAttributes dva( diagram->dataValueAttributes( index ) );
472 const QString text = formatDataValueText( dva, index, value );
473 paintDataValueText( painter, dva, pos, value >= 0.0, text,
474 justCalculateRect, cumulatedBoundingRect );
475 }
476
paintDataValueText(QPainter * painter,const DataValueAttributes & attrs,const QPointF & pos,bool valueIsPositive,const QString & text,bool justCalculateRect,QRectF * cumulatedBoundingRect)477 void AbstractDiagram::Private::paintDataValueText(
478 QPainter* painter,
479 const DataValueAttributes& attrs,
480 const QPointF& pos,
481 bool valueIsPositive,
482 const QString& text,
483 bool justCalculateRect /* = false */,
484 QRectF* cumulatedBoundingRect /* = 0 */ )
485 {
486 if ( !attrs.isVisible() ) {
487 return;
488 }
489
490 const TextAttributes ta( attrs.textAttributes() );
491 if ( !ta.isVisible() || ( !attrs.showRepetitiveDataLabels() && prevPaintedDataValueText == text ) ) {
492 return;
493 }
494 prevPaintedDataValueText = text;
495
496 QTextDocument doc;
497 doc.setDocumentMargin( 0.0 );
498 if ( Qt::mightBeRichText( text ) ) {
499 doc.setHtml( text );
500 } else {
501 doc.setPlainText( text );
502 }
503
504 const QFont calculatedFont( ta.calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) );
505
506 const PainterSaver painterSaver( painter );
507 painter->setPen( PrintingParameters::scalePen( ta.pen() ) );
508
509 doc.setDefaultFont( calculatedFont );
510 QAbstractTextDocumentLayout::PaintContext context;
511 context.palette = diagram->palette();
512 context.palette.setColor( QPalette::Text, ta.pen().color() );
513
514 QAbstractTextDocumentLayout* const layout = doc.documentLayout();
515 layout->setPaintDevice( painter->device() );
516
517 painter->translate( pos.x(), pos.y() );
518 int rotation = ta.rotation();
519 if ( !valueIsPositive && attrs.mirrorNegativeValueTextRotation() ) {
520 rotation *= -1;
521 }
522 painter->rotate( rotation );
523
524 // do overlap detection "as seen by the painter"
525 QTransform transform = painter->worldTransform();
526
527 bool drawIt = true;
528 // note: This flag can be set differently for every label text!
529 // In theory a user could e.g. have some small red text on one of the
530 // values that she wants to have written in any case - so we just
531 // do not test if such texts would cover some of the others.
532 if ( !attrs.showOverlappingDataLabels() ) {
533 const QRectF br( layout->frameBoundingRect( doc.rootFrame() ) );
534 QPolygon pr = transform.mapToPolygon( br.toRect() );
535 // Using QPainterPath allows us to use intersects() (which has many early-exits)
536 // instead of QPolygon::intersected (which calculates a slow and precise intersection polygon)
537 QPainterPath path;
538 path.addPolygon( pr );
539
540 // iterate backwards because recently added items are more likely to overlap, so we spend
541 // less time checking irrelevant items when there is overlap
542 for ( int i = alreadyDrawnDataValueTexts.count() - 1; i >= 0; i-- ) {
543 if ( alreadyDrawnDataValueTexts.at( i ).intersects( path ) ) {
544 // qDebug() << "not painting this label due to overlap";
545 drawIt = false;
546 break;
547 }
548 }
549 if ( drawIt ) {
550 alreadyDrawnDataValueTexts << path;
551 }
552 }
553
554 if ( drawIt ) {
555 QRectF rect = layout->frameBoundingRect( doc.rootFrame() );
556 if ( cumulatedBoundingRect ) {
557 (*cumulatedBoundingRect) |= transform.mapRect( rect );
558 }
559 if ( !justCalculateRect ) {
560 bool paintBack = false;
561 BackgroundAttributes back( attrs.backgroundAttributes() );
562 if ( back.isVisible() ) {
563 paintBack = true;
564 painter->setBrush( back.brush() );
565 } else {
566 painter->setBrush( QBrush() );
567 }
568
569 qreal radius = 0.0;
570 FrameAttributes frame( attrs.frameAttributes() );
571 if ( frame.isVisible() ) {
572 paintBack = true;
573 painter->setPen( frame.pen() );
574 radius = frame.cornerRadius();
575 }
576
577 if ( paintBack ) {
578 QRectF borderRect( QPointF( 0, 0 ), rect.size() );
579 painter->drawRoundedRect( borderRect, radius, radius );
580 }
581 layout->draw( painter, context );
582 }
583 }
584 }
585
indexAt(const QPoint & point) const586 QModelIndex AbstractDiagram::Private::indexAt( const QPoint& point ) const
587 {
588 QModelIndexList l = indexesAt( point );
589 std::sort(l.begin(), l.end());
590 if ( !l.isEmpty() )
591 return l.first();
592 else
593 return QModelIndex();
594 }
595
indexesAt(const QPoint & point) const596 QModelIndexList AbstractDiagram::Private::indexesAt( const QPoint& point ) const
597 {
598 return reverseMapper.indexesAt( point ); // which could be empty
599 }
600
indexesIn(const QRect & rect) const601 QModelIndexList AbstractDiagram::Private::indexesIn( const QRect& rect ) const
602 {
603 return reverseMapper.indexesIn( rect );
604 }
605
aggregatedAttrs(const QModelIndex & index,const CartesianDiagramDataCompressor::CachePosition * position) const606 CartesianDiagramDataCompressor::AggregatedDataValueAttributes AbstractDiagram::Private::aggregatedAttrs(
607 const QModelIndex& index,
608 const CartesianDiagramDataCompressor::CachePosition* position ) const
609 {
610 Q_UNUSED( position ); // used by cartesian diagrams only
611 CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs;
612 allAttrs[index] = diagram->dataValueAttributes( index );
613 return allAttrs;
614 }
615
setDatasetAttrs(int dataset,const QVariant & data,int role)616 void AbstractDiagram::Private::setDatasetAttrs( int dataset, const QVariant& data, int role )
617 {
618 // To store attributes for a dataset, we use the first column
619 // that's associated with it. (i.e., with a dataset dimension
620 // of two, the column of the keys). In most cases however, there's
621 // only one data dimension, and thus also only one column per data set.
622 int column = dataset * datasetDimension;
623
624 // For DataHiddenRole, also store the flag in the other data points that belong to this data set,
625 // otherwise it's impossible to hide data points in a plotter diagram because there will always
626 // be one model index that belongs to this data point that is not hidden.
627 // For more details on how hiding works, see the data compressor.
628 // Also see KDCH-503 for which this is a workaround.
629 int columnSpan = role == DataHiddenRole ? datasetDimension : 1;
630
631 for ( int i = 0; i < columnSpan; i++ ) {
632 attributesModel->setHeaderData( column + i, Qt::Horizontal, data, role );
633 }
634 }
635
datasetAttrs(int dataset,int role) const636 QVariant AbstractDiagram::Private::datasetAttrs( int dataset, int role ) const
637 {
638 // See setDataSetAttrs for explanation of column
639 int column = dataset * datasetDimension;
640 return attributesModel->headerData( column, Qt::Horizontal, role );
641 }
642
resetDatasetAttrs(int dataset,int role)643 void AbstractDiagram::Private::resetDatasetAttrs( int dataset, int role )
644 {
645 // See setDataSetAttrs for explanation of column
646 int column = dataset * datasetDimension;
647 attributesModel->resetHeaderData( column, Qt::Horizontal, role );
648 }
649
isTransposed() const650 bool AbstractDiagram::Private::isTransposed() const
651 {
652 // Determine the diagram that specifies the orientation.
653 // That diagram is the reference diagram, if it exists, or otherwise the diagram itself.
654 // Note: In KChart 2.3 or earlier, only a bar diagram can be transposed.
655 const AbstractCartesianDiagram* refDiagram = qobject_cast< const AbstractCartesianDiagram * >( diagram );
656 if ( !refDiagram ) {
657 return false;
658 }
659 if ( refDiagram->referenceDiagram() ) {
660 refDiagram = refDiagram->referenceDiagram();
661 }
662 const BarDiagram* barDiagram = qobject_cast< const BarDiagram* >( refDiagram );
663 if ( !barDiagram ) {
664 return false;
665 }
666 return barDiagram->orientation() == Qt::Horizontal;
667 }
668
LineAttributesInfo()669 LineAttributesInfo::LineAttributesInfo()
670 {
671 }
672
LineAttributesInfo(const QModelIndex & _index,const QPointF & _value,const QPointF & _nextValue)673 LineAttributesInfo::LineAttributesInfo( const QModelIndex& _index, const QPointF& _value, const QPointF& _nextValue )
674 : index( _index )
675 , value ( _value )
676 , nextValue ( _nextValue )
677 {
678 }
679