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