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

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/