KD Chart 2  [rev.2.7]
KDChartCartesianAxis.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2020 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 #include "KDChartCartesianAxis.h"
24 #include "KDChartCartesianAxis_p.h"
25 
26 #include <cmath>
27 
28 #include <QtDebug>
29 #include <QPainter>
30 #include <QPen>
31 #include <QBrush>
32 #include <QApplication>
33 
34 #include "KDChartPaintContext.h"
35 #include "KDChartChart.h"
37 #include "KDChartAbstractDiagram_p.h"
38 #include "KDChartAbstractGrid.h"
39 #include "KDChartPainterSaver_p.h"
40 #include "KDChartLayoutItems.h"
41 #include "KDChartBarDiagram.h"
42 #include "KDChartStockDiagram.h"
43 #include "KDChartLineDiagram.h"
45 
46 #include <KDABLibFakes>
47 
48 using namespace KDChart;
49 
50 #define d (d_func())
51 
52 static qreal slightlyLessThan( qreal r )
53 {
54  if ( r == 0.0 ) {
55  // scale down the epsilon somewhat arbitrarily
56  return r - std::numeric_limits< qreal >::epsilon() * 1e-6;
57  }
58  // scale the epsilon so that it (hopefully) changes at least the least significant bit of r
59  qreal diff = qAbs( r ) * std::numeric_limits< qreal >::epsilon() * 2.0;
60  return r - diff;
61 }
62 
63 static int numSignificantDecimalPlaces( qreal floatNumber )
64 {
65  static const int maxPlaces = 15;
66  QString sample = QString::number( floatNumber, 'f', maxPlaces ).section( QLatin1Char('.'), 1, 2 );
67  int ret = maxPlaces;
68  for ( ; ret > 0; ret-- ) {
69  if ( sample[ ret - 1 ] != QLatin1Char( '0' ) ) {
70  break;
71  }
72  }
73  return ret;
74 }
75 
76 // Feature idea: In case of numeric labels, consider limiting the possible values of majorThinningFactor
77 // to something like {1, 2, 5} * 10^n. Or even better, something that achieves round values in the
78 // remaining labels.
79 
80 TickIterator::TickIterator( CartesianAxis* a, CartesianCoordinatePlane* plane, uint majorThinningFactor,
81  bool omitLastTick )
82  : m_axis( a ),
83  m_majorThinningFactor( majorThinningFactor ),
84  m_majorLabelCount( 0 ),
85  m_type( NoTick )
86 {
87  // deal with the things that are specific to axes (like annotations), before the generic init().
88  const CartesianAxis::Private *axisPriv = CartesianAxis::Private::get( a );
89  XySwitch xy( axisPriv->isVertical() );
90  m_dimension = xy( plane->gridDimensionsList().first(), plane->gridDimensionsList().last() );
91  if ( omitLastTick ) {
92  // In bar and stock charts the last X tick is a fencepost with no associated value, which is
93  // convenient for grid painting. Here we have to manually exclude it to avoid overpainting.
94  m_dimension.end -= m_dimension.stepWidth;
95  }
96 
97  m_annotations = axisPriv->annotations;
98  m_customTicks = axisPriv->customTicksPositions;
99 
100  const qreal inf = std::numeric_limits< qreal >::infinity();
101 
102  if ( m_customTicks.count() ) {
103  qSort( m_customTicks.begin(), m_customTicks.end() );
104  m_customTickIndex = 0;
105  m_customTick = m_customTicks.at( m_customTickIndex );
106  } else {
107  m_customTickIndex = -1;
108  m_customTick = inf;
109  }
110 
111  if ( m_majorThinningFactor > 1 && hasShorterLabels() ) {
112  m_manualLabelTexts = m_axis->shortLabels();
113  } else {
114  m_manualLabelTexts = m_axis->labels();
115  }
116  m_manualLabelIndex = m_manualLabelTexts.isEmpty() ? -1 : 0;
117 
118  if ( !m_dimension.isCalculated ) {
119  // ### depending on the data, it is difficult to impossible to choose anchors (where ticks
120  // corresponding to the header labels are) on the ordinate or even the abscissa with
121  // 2-dimensional data. this should be somewhat mitigated by isCalculated only being false
122  // when header data labels should work, at least that seems to be what the code that sets up
123  // the dimensions is trying to do.
124  QStringList dataHeaderLabels;
125  AbstractDiagram* const dia = plane->diagram();
126  dataHeaderLabels = dia->itemRowLabels();
127  if ( !dataHeaderLabels.isEmpty() ) {
128  AttributesModel* model = dia->attributesModel();
129  const int anchorCount = model->rowCount( QModelIndex() );
130  if ( anchorCount == dataHeaderLabels.count() ) {
131  for ( int i = 0; i < anchorCount; i++ ) {
132  // ### ordinal number as anchor point generally only works for 1-dimensional data
133  m_dataHeaderLabels.insert( qreal( i ), dataHeaderLabels.at( i ) );
134  }
135  }
136  }
137  }
138 
139  bool hasMajorTicks = m_axis->rulerAttributes().showMajorTickMarks();
140  bool hasMinorTicks = m_axis->rulerAttributes().showMinorTickMarks();
141 
142  init( xy.isY, hasMajorTicks, hasMinorTicks, plane );
143 }
144 
146 {
147  QMap< qreal, QString > annotations;
148  Q_FOREACH( const AbstractDiagram *diagram, plane->diagrams() ) {
149  const AbstractCartesianDiagram *cd = qobject_cast< const AbstractCartesianDiagram* >( diagram );
150  if ( !cd ) {
151  continue;
152  }
153  Q_FOREACH( const CartesianAxis *axis, cd->axes() ) {
154  const CartesianAxis::Private *axisPriv = CartesianAxis::Private::get( axis );
155  if ( axisPriv->isVertical() == isY ) {
156  annotations.unite( axisPriv->annotations );
157  }
158  }
159  }
160  return annotations;
161 }
162 
163 TickIterator::TickIterator( bool isY, const DataDimension& dimension, bool useAnnotationsForTicks,
164  bool hasMajorTicks, bool hasMinorTicks, CartesianCoordinatePlane* plane )
165  : m_axis( 0 ),
166  m_dimension( dimension ),
167  m_majorThinningFactor( 1 ),
168  m_majorLabelCount( 0 ),
169  m_customTickIndex( -1 ),
170  m_manualLabelIndex( -1 ),
171  m_type( NoTick ),
172  m_customTick( std::numeric_limits< qreal >::infinity() )
173 {
174  if ( useAnnotationsForTicks ) {
175  m_annotations = allAxisAnnotations( plane, isY );
176  }
177  init( isY, hasMajorTicks, hasMinorTicks, plane );
178 }
179 
180 void TickIterator::init( bool isY, bool hasMajorTicks, bool hasMinorTicks,
181  CartesianCoordinatePlane* plane )
182 {
183  Q_ASSERT( std::numeric_limits< qreal >::has_infinity );
184 
185  m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
186  // sanity check against infinite loops
187  hasMajorTicks = hasMajorTicks && ( m_dimension.stepWidth > 0 || m_isLogarithmic );
188  hasMinorTicks = hasMinorTicks && ( m_dimension.subStepWidth > 0 || m_isLogarithmic );
189 
190  XySwitch xy( isY );
191 
192  GridAttributes gridAttributes = plane->gridAttributes( xy( Qt::Horizontal, Qt::Vertical ) );
193  m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
194  if ( !m_isLogarithmic ) {
195  // adjustedLowerUpperRange() is intended for use with linear scaling; specifically it would
196  // round lower bounds < 1 to 0.
197 
198  const bool fixedRange = xy( plane->autoAdjustHorizontalRangeToData(),
199  plane->autoAdjustVerticalRangeToData() ) >= 100;
200  const bool adjustLower = gridAttributes.adjustLowerBoundToGrid() && !fixedRange;
201  const bool adjustUpper = gridAttributes.adjustUpperBoundToGrid() && !fixedRange;
202  m_dimension = AbstractGrid::adjustedLowerUpperRange( m_dimension, adjustLower, adjustUpper );
203 
204  m_decimalPlaces = numSignificantDecimalPlaces( m_dimension.stepWidth );
205  } else {
206  // the number of significant decimal places for each label naturally varies with logarithmic scaling
207  m_decimalPlaces = -1;
208  }
209 
210  const qreal inf = std::numeric_limits< qreal >::infinity();
211 
212  // try to place m_position just in front of the first tick to be drawn so that operator++()
213  // can be used to find the first tick
214  if ( m_isLogarithmic ) {
215  if ( ISNAN( m_dimension.start ) || ISNAN( m_dimension.end ) ) {
216  // this can happen in a spurious paint operation before everything is set up;
217  // just bail out to avoid an infinite loop in that case.
218  m_dimension.start = 0.0;
219  m_dimension.end = 0.0;
220  m_position = inf;
221  m_majorTick = inf;
222  m_minorTick = inf;
223  } else if ( m_dimension.start >= 0 ) {
224  m_position = m_dimension.start ? pow( 10.0, floor( log10( m_dimension.start ) ) - 1.0 )
225  : 1e-6;
226  m_majorTick = hasMajorTicks ? m_position : inf;
227  m_minorTick = hasMinorTicks ? m_position * 20.0 : inf;
228  } else {
229  m_position = -pow( 10.0, ceil( log10( -m_dimension.start ) ) + 1.0 );
230  m_majorTick = hasMajorTicks ? m_position : inf;
231  m_minorTick = hasMinorTicks ? m_position * 0.09 : inf;
232  }
233  } else {
234  m_majorTick = hasMajorTicks ? m_dimension.start : inf;
235  m_minorTick = hasMinorTicks ? m_dimension.start : inf;
236  m_position = slightlyLessThan( m_dimension.start );
237  }
238 
239  ++( *this );
240 }
241 
242 bool TickIterator::areAlmostEqual( qreal r1, qreal r2 ) const
243 {
244  if ( !m_isLogarithmic ) {
245  qreal span = m_dimension.end - m_dimension.start;
246  if ( span == 0 ) {
247  // When start == end, we still want to show one tick if possible,
248  // which needs this function to perform a reasonable comparison.
249  span = qFuzzyIsNull( m_dimension.start) ? 1 : qAbs( m_dimension.start );
250  }
251  return qAbs( r2 - r1 ) < ( span ) * 1e-6;
252  } else {
253  return qAbs( r2 - r1 ) < qMax( qAbs( r1 ), qAbs( r2 ) ) * 0.01;
254  }
255 }
256 
257 bool TickIterator::isHigherPrecedence( qreal importantTick, qreal unimportantTick ) const
258 {
259  return importantTick != std::numeric_limits< qreal >::infinity() &&
260  ( importantTick <= unimportantTick || areAlmostEqual( importantTick, unimportantTick ) );
261 }
262 
263 void TickIterator::computeMajorTickLabel( int decimalPlaces )
264 {
265  if ( m_manualLabelIndex >= 0 ) {
266  m_text = m_manualLabelTexts[ m_manualLabelIndex++ ];
267  if ( m_manualLabelIndex >= m_manualLabelTexts.count() ) {
268  // manual label texts repeat if there are less label texts than ticks on an axis
269  m_manualLabelIndex = 0;
270  }
271  m_type = m_majorThinningFactor > 1 ? MajorTickManualShort : MajorTickManualLong;
272  } else {
273  // if m_axis is null, we are dealing with grid lines. grid lines never need labels.
274  if ( m_axis && ( m_majorLabelCount++ % m_majorThinningFactor ) == 0 ) {
276  m_dataHeaderLabels.lowerBound( slightlyLessThan( m_position ) );
277 
278  if ( it != m_dataHeaderLabels.constEnd() && areAlmostEqual( it.key(), m_position ) ) {
279  m_text = it.value();
280  m_type = MajorTickHeaderDataLabel;
281  } else {
282  // 'f' to avoid exponential notation for large numbers, consistent with data value text
283  if ( decimalPlaces < 0 ) {
284  decimalPlaces = numSignificantDecimalPlaces( m_position );
285  }
286  m_text = QString::number( m_position, 'f', decimalPlaces );
287  m_type = MajorTick;
288  }
289  } else {
290  m_text.clear();
291  m_type = MajorTick;
292  }
293  }
294 }
295 
296 void TickIterator::operator++()
297 {
298  if ( isAtEnd() ) {
299  return;
300  }
301  const qreal inf = std::numeric_limits< qreal >::infinity();
302 
303  // make sure to find the next tick at a value strictly greater than m_position
304 
305  if ( !m_annotations.isEmpty() ) {
306  QMap< qreal, QString >::ConstIterator it = m_annotations.upperBound( m_position );
307  if ( it != m_annotations.constEnd() ) {
308  m_position = it.key();
309  m_text = it.value();
310  m_type = CustomTick;
311  } else {
312  m_position = inf;
313  }
314  } else if ( !m_isLogarithmic && m_dimension.stepWidth * 1e6 <
315  qMax( qAbs( m_dimension.start ), qAbs( m_dimension.end ) ) ) {
316  // If the step width is too small to increase m_position at all, we get an infinite loop.
317  // This usually happens when m_dimension.start == m_dimension.end and both are very large.
318  // When start == end, the step width defaults to 1, and it doesn't scale with start or end.
319  // So currently, we bail and show no tick at all for empty ranges > 10^6, but at least we don't hang.
320  m_position = inf;
321  } else {
322  // advance the calculated ticks
323  if ( m_isLogarithmic ) {
324  while ( m_majorTick <= m_position ) {
325  m_majorTick *= m_position >= 0 ? 10 : 0.1;
326  }
327  while ( m_minorTick <= m_position ) {
328  // the next major tick position should be greater than this
329  m_minorTick += m_majorTick * ( m_position >= 0 ? 0.1 : 1.0 );
330  }
331  } else {
332  while ( m_majorTick <= m_position ) {
333  m_majorTick += m_dimension.stepWidth;
334  }
335  while ( m_minorTick <= m_position ) {
336  m_minorTick += m_dimension.subStepWidth;
337  }
338  }
339 
340  while ( m_customTickIndex >= 0 && m_customTick <= m_position ) {
341  if ( ++m_customTickIndex >= m_customTicks.count() ) {
342  m_customTickIndex = -1;
343  m_customTick = inf;
344  break;
345  }
346  m_customTick = m_customTicks.at( m_customTickIndex );
347  }
348 
349  // now see which kind of tick we'll have
350  if ( isHigherPrecedence( m_customTick, m_majorTick ) && isHigherPrecedence( m_customTick, m_minorTick ) ) {
351  m_position = m_customTick;
352  computeMajorTickLabel( -1 );
353  // override the MajorTick type here because those tick's labels are collision-tested, which we don't want
354  // for custom ticks. they may be arbitrarily close to other ticks, causing excessive label thinning.
355  if ( m_type == MajorTick ) {
356  m_type = CustomTick;
357  }
358  } else if ( isHigherPrecedence( m_majorTick, m_minorTick ) ) {
359  m_position = m_majorTick;
360  if ( m_minorTick != inf ) {
361  // realign minor to major
362  m_minorTick = m_majorTick;
363  }
364  computeMajorTickLabel( m_decimalPlaces );
365  } else if ( m_minorTick != inf ) {
366  m_position = m_minorTick;
367  m_text.clear();
368  m_type = MinorTick;
369  } else {
370  m_position = inf;
371  }
372  }
373 
374  if ( m_position > m_dimension.end || ISNAN( m_position ) ) {
375  m_position = inf; // make isAtEnd() return true
376  m_text.clear();
377  m_type = NoTick;
378  }
379 }
380 
382  : AbstractAxis ( new Private( diagram, this ), diagram )
383 {
384  init();
385 }
386 
388 {
389  // when we remove the first axis it will unregister itself and
390  // propagate the next one to the primary, thus the while loop
391  while ( d->mDiagram ) {
392  AbstractCartesianDiagram *cd = qobject_cast< AbstractCartesianDiagram* >( d->mDiagram );
393  cd->takeAxis( this );
394  }
395  KDAB_FOREACH( AbstractDiagram *diagram, d->secondaryDiagrams ) {
397  cd->takeAxis( this );
398  }
399 }
400 
401 void CartesianAxis::init()
402 {
403  d->customTickLength = 3;
404  d->position = Bottom;
406  connect( this, SIGNAL( coordinateSystemChanged() ), SLOT( coordinateSystemChanged() ) );
407 }
408 
409 
410 bool CartesianAxis::compare( const CartesianAxis* other ) const
411 {
412  if ( other == this ) {
413  return true;
414  }
415  if ( !other ) {
416  return false;
417  }
418  return AbstractAxis::compare( other ) && ( position() == other->position() ) &&
419  ( titleText() == other->titleText() ) &&
420  ( titleTextAttributes() == other->titleTextAttributes() );
421 }
422 
423 void CartesianAxis::coordinateSystemChanged()
424 {
425  layoutPlanes();
426 }
427 
428 void CartesianAxis::setTitleText( const QString& text )
429 {
430  d->titleText = text;
432  layoutPlanes();
433 }
434 
436 {
437  return d->titleText;
438 }
439 
441 {
442  d->titleTextAttributes = a;
443  d->useDefaultTextAttributes = false;
445  layoutPlanes();
446 }
447 
449 {
452  Measure me( ta.fontSize() );
453  me.setValue( me.value() * 1.5 );
454  ta.setFontSize( me );
455  return ta;
456  }
457  return d->titleTextAttributes;
458 }
459 
461 {
462  d->useDefaultTextAttributes = true;
464  layoutPlanes();
465 }
466 
468 {
469  return d->useDefaultTextAttributes;
470 }
471 
473 {
474  if ( d->position == p ) {
475  return;
476  }
477  d->position = p;
478  // Invalidating size is not always necessary if both old and new positions are horizontal or both
479  // vertical, but in practice there could be small differences due to who-knows-what, so always adapt
480  // to the possibly new size. Changing position is expensive anyway.
482  layoutPlanes();
483 }
484 
485 #if QT_VERSION < 0x040400 || defined(Q_COMPILER_MANGLES_RETURN_TYPE)
486 const
487 #endif
489 {
490  return d->position;
491 }
492 
494 {
495  if ( ! d->diagram() || ! d->diagram()->coordinatePlane() ) {
496  return;
497  }
498  AbstractCoordinatePlane* plane = d->diagram()->coordinatePlane();
499  if ( plane ) {
500  plane->layoutPlanes();
501  }
502 }
503 
505 {
506  const AbstractCartesianDiagram * dia =
507  qobject_cast< const AbstractCartesianDiagram * >( diagram );
508  if ( dia && dia->referenceDiagram() )
509  dia = dia->referenceDiagram();
510  return qobject_cast< const BarDiagram* >( dia ) != 0;
511 }
512 
514 {
515  const AbstractCartesianDiagram * dia =
516  qobject_cast< const AbstractCartesianDiagram * >( diagram );
517  if ( dia && dia->referenceDiagram() )
518  dia = dia->referenceDiagram();
519  if ( qobject_cast< const BarDiagram* >( dia ) )
520  return true;
521  if ( qobject_cast< const StockDiagram* >( dia ) )
522  return true;
523 
524  const LineDiagram * lineDiagram = qobject_cast< const LineDiagram* >( dia );
525  return lineDiagram && lineDiagram->centerDataPoints();
526 }
527 
529 {
530  const Qt::Orientation diagramOrientation = referenceDiagramIsBarDiagram( d->diagram() ) ? ( ( BarDiagram* )( d->diagram() ) )->orientation()
531  : Qt::Vertical;
532  return diagramOrientation == Qt::Vertical ? position() == Bottom || position() == Top
533  : position() == Left || position() == Right;
534 }
535 
537 {
538  return !isAbscissa();
539 }
540 
541 void CartesianAxis::paint( QPainter* painter )
542 {
543  if ( !d->diagram() || !d->diagram()->coordinatePlane() ) {
544  return;
545  }
546  PaintContext ctx;
547  ctx.setPainter ( painter );
548  AbstractCoordinatePlane *const plane = d->diagram()->coordinatePlane();
549  ctx.setCoordinatePlane( plane );
550 
551  ctx.setRectangle( QRectF( areaGeometry() ) );
552  PainterSaver painterSaver( painter );
553 
554  // enable clipping only when required due to zoom, because it slows down painting
555  // (the alternative to clipping when zoomed in requires much more work to paint just the right area)
556  const qreal zoomFactor = d->isVertical() ? plane->zoomFactorY() : plane->zoomFactorX();
557  if ( zoomFactor > 1.0 ) {
558  painter->setClipRegion( areaGeometry().adjusted( - d->amountOfLeftOverlap - 1, - d->amountOfTopOverlap - 1,
559  d->amountOfRightOverlap + 1, d->amountOfBottomOverlap + 1 ) );
560  }
561  paintCtx( &ctx );
562 }
563 
564 const TextAttributes CartesianAxis::Private::titleTextAttributesWithAdjustedRotation() const
565 {
567  int rotation = titleTA.rotation();
568  if ( position == Left || position == Right ) {
569  rotation += 270;
570  }
571  if ( rotation >= 360 ) {
572  rotation -= 360;
573  }
574  // limit the allowed values to 0, 90, 180, 270
575  rotation = ( rotation / 90 ) * 90;
576  titleTA.setRotation( rotation );
577  return titleTA;
578 }
579 
580 QString CartesianAxis::Private::customizedLabelText( const QString& text, Qt::Orientation orientation,
581  qreal value ) const
582 {
583  // ### like in the old code, using int( value ) as column number...
584  QString withUnits = diagram()->unitPrefix( int( value ), orientation, true ) +
585  text +
586  diagram()->unitSuffix( int( value ), orientation, true );
587  return axis()->customizedLabel( withUnits );
588 }
589 
590 void CartesianAxis::setTitleSpace( qreal axisTitleSpace )
591 {
592  d->axisTitleSpace = axisTitleSpace;
593 }
594 
596 {
597  return d->axisTitleSpace;
598 }
599 
600 void CartesianAxis::setTitleSize( qreal value )
601 {
602  Q_UNUSED( value )
603  // ### remove me
604 }
605 
607 {
608  // ### remove me
609  return 1.0;
610 }
611 
612 void CartesianAxis::Private::drawTitleText( QPainter* painter, CartesianCoordinatePlane* plane,
613  const QRect& geoRect ) const
614 {
615  const TextAttributes titleTA( titleTextAttributesWithAdjustedRotation() );
616  if ( titleTA.isVisible() ) {
618  Qt::AlignHCenter | Qt::AlignVCenter );
619  QPointF point;
620  QSize size = titleItem.sizeHint();
621  switch ( position ) {
622  case Top:
623  point.setX( geoRect.left() + geoRect.width() / 2 );
624  point.setY( geoRect.top() + ( size.height() / 2 ) / axisTitleSpace );
625  size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
626  break;
627  case Bottom:
628  point.setX( geoRect.left() + geoRect.width() / 2 );
629  point.setY( geoRect.bottom() - ( size.height() / 2 ) / axisTitleSpace );
630  size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
631  break;
632  case Left:
633  point.setX( geoRect.left() + ( size.width() / 2 ) / axisTitleSpace );
634  point.setY( geoRect.top() + geoRect.height() / 2 );
635  size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
636  break;
637  case Right:
638  point.setX( geoRect.right() - ( size.width() / 2 ) / axisTitleSpace );
639  point.setY( geoRect.top() + geoRect.height() / 2 );
640  size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
641  break;
642  }
643  const PainterSaver painterSaver( painter );
644  painter->setClipping( false );
645  painter->translate( point );
646  titleItem.setGeometry( QRect( QPoint( -size.width() / 2, -size.height() / 2 ), size ) );
647  titleItem.paint( painter );
648  }
649 }
650 
651 bool CartesianAxis::Private::isVertical() const
652 {
653  return axis()->isAbscissa() == AbstractDiagram::Private::get( diagram() )->isTransposed();
654 }
655 
657 {
658  Q_ASSERT_X ( d->diagram(), "CartesianAxis::paint",
659  "Function call not allowed: The axis is not assigned to any diagram." );
660 
661  CartesianCoordinatePlane* plane = dynamic_cast<CartesianCoordinatePlane*>( context->coordinatePlane() );
662  Q_ASSERT_X ( plane, "CartesianAxis::paint",
663  "Bad function call: PaintContext::coordinatePlane() NOT a cartesian plane." );
664 
665  // note: Not having any data model assigned is no bug
666  // but we can not draw an axis then either.
667  if ( !d->diagram()->model() ) {
668  return;
669  }
670 
671  const bool centerTicks = referenceDiagramNeedsCenteredAbscissaTicks( d->diagram() ) && isAbscissa();
672 
673  XySwitch geoXy( d->isVertical() );
674 
675  QPainter* const painter = context->painter();
676 
677  // determine the position of the axis (also required for labels) and paint it
678 
679  qreal transversePosition = signalingNaN; // in data space
680  // the next one describes an additional shift in screen space; it is unfortunately required to
681  // make axis sharing work, which uses the areaGeometry() to override the position of the axis.
682  qreal transverseScreenSpaceShift = signalingNaN;
683  {
684  // determine the unadulterated position in screen space
685 
686  DataDimension dimX = plane->gridDimensionsList().first();
687  DataDimension dimY = plane->gridDimensionsList().last();
688  QPointF start( dimX.start, dimY.start );
689  QPointF end( dimX.end, dimY.end );
690  // consider this: you can turn a diagonal line into a horizontal or vertical line on any
691  // edge by changing just one of its four coordinates.
692  switch ( position() ) {
694  end.setY( dimY.start );
695  break;
696  case CartesianAxis::Top:
697  start.setY( dimY.end );
698  break;
699  case CartesianAxis::Left:
700  end.setX( dimX.start );
701  break;
703  start.setX( dimX.end );
704  break;
705  }
706 
707  transversePosition = geoXy( start.y(), start.x() );
708 
709  QPointF transStart = plane->translate( start );
710  QPointF transEnd = plane->translate( end );
711 
712  // an externally set areaGeometry() moves the axis position transversally; the shift is
713  // nonzero only when this is a shared axis
714 
715  const QRect geo = areaGeometry();
716  switch ( position() ) {
718  transverseScreenSpaceShift = geo.top() - transStart.y();
719  break;
720  case CartesianAxis::Top:
721  transverseScreenSpaceShift = geo.bottom() - transStart.y();
722  break;
723  case CartesianAxis::Left:
724  transverseScreenSpaceShift = geo.right() - transStart.x();
725  break;
727  transverseScreenSpaceShift = geo.left() - transStart.x();
728  break;
729  }
730 
731  geoXy.lvalue( transStart.ry(), transStart.rx() ) += transverseScreenSpaceShift;
732  geoXy.lvalue( transEnd.ry(), transEnd.rx() ) += transverseScreenSpaceShift;
733 
734  if ( rulerAttributes().showRulerLine() ) {
735  bool clipSaved = context->painter()->hasClipping();
736  painter->setClipping( false );
737  painter->drawLine( transStart, transEnd );
738  painter->setClipping( clipSaved );
739  }
740  }
741 
742  // paint ticks and labels
743 
744  TextAttributes labelTA = textAttributes();
745  RulerAttributes rulerAttr = rulerAttributes();
746 
747  int labelThinningFactor = 1;
748  // TODO: label thinning also when grid line distance < 4 pixels, not only when labels collide
749  TextLayoutItem *tickLabel = new TextLayoutItem( QString(), labelTA, plane->parent(),
751  TextLayoutItem *prevTickLabel = new TextLayoutItem( QString(), labelTA, plane->parent(),
753  QPointF prevTickLabelPos;
754  enum {
755  Layout = 0,
756  Painting,
757  Done
758  };
759  for ( int step = labelTA.isVisible() ? Layout : Painting; step < Done; step++ ) {
760  bool skipFirstTick = !rulerAttr.showFirstTick();
761  bool isFirstLabel = true;
762  for ( TickIterator it( this, plane, labelThinningFactor, centerTicks ); !it.isAtEnd(); ++it ) {
763  if ( skipFirstTick ) {
764  skipFirstTick = false;
765  continue;
766  }
767 
768  const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
769  QPointF onAxis = plane->translate( geoXy( QPointF( drawPos, transversePosition ) ,
770  QPointF( transversePosition, drawPos ) ) );
771  geoXy.lvalue( onAxis.ry(), onAxis.rx() ) += transverseScreenSpaceShift;
772  const bool isOutwardsPositive = position() == Bottom || position() == Right;
773 
774  // paint the tick mark
775 
776  QPointF tickEnd = onAxis;
777  qreal tickLen = it.type() == TickIterator::CustomTick ?
778  d->customTickLength : tickLength( it.type() == TickIterator::MinorTick );
779  geoXy.lvalue( tickEnd.ry(), tickEnd.rx() ) += isOutwardsPositive ? tickLen : -tickLen;
780 
781  // those adjustments are required to paint the ticks exactly on the axis and of the right length
782  if ( position() == Top ) {
783  onAxis.ry() += 1;
784  tickEnd.ry() += 1;
785  } else if ( position() == Left ) {
786  tickEnd.rx() += 1;
787  }
788 
789  if ( step == Painting ) {
790  painter->save();
791  if ( rulerAttr.hasTickMarkPenAt( it.position() ) ) {
792  painter->setPen( rulerAttr.tickMarkPen( it.position() ) );
793  } else {
794  painter->setPen( it.type() == TickIterator::MinorTick ? rulerAttr.minorTickMarkPen()
795  : rulerAttr.majorTickMarkPen() );
796  }
797  painter->drawLine( onAxis, tickEnd );
798  painter->restore();
799  }
800 
801  if ( it.text().isEmpty() || !labelTA.isVisible() ) {
802  // the following code in the loop is only label painting, so skip it
803  continue;
804  }
805 
806  // paint the label
807 
808  QString text = it.text();
809  if ( it.type() == TickIterator::MajorTick ) {
810  // add unit prefixes and suffixes, then customize
811  text = d->customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
812  } else if ( it.type() == TickIterator::MajorTickHeaderDataLabel ) {
813  // unit prefixes and suffixes have already been added in this case - only customize
814  text = customizedLabel( text );
815  }
816 
817  tickLabel->setText( text );
818  QSizeF size = QSizeF( tickLabel->sizeHint() );
819  QPolygon labelPoly = tickLabel->boundingPolygon();
820  Q_ASSERT( labelPoly.count() == 4 );
821 
822  // for alignment, find the label polygon edge "most parallel" and closest to the axis
823 
824  int axisAngle = 0;
825  switch ( position() ) {
826  case Bottom:
827  axisAngle = 0; break;
828  case Top:
829  axisAngle = 180; break;
830  case Right:
831  axisAngle = 270; break;
832  case Left:
833  axisAngle = 90; break;
834  default:
835  Q_ASSERT( false );
836  }
837  // the left axis is not actually pointing down and the top axis not actually pointing
838  // left, but their corresponding closest edges of a rectangular unrotated label polygon are.
839 
840  int relAngle = axisAngle - labelTA.rotation() + 45;
841  if ( relAngle < 0 ) {
842  relAngle += 360;
843  }
844  int polyCorner1 = relAngle / 90;
845  QPoint p1 = labelPoly.at( polyCorner1 );
846  QPoint p2 = labelPoly.at( polyCorner1 == 3 ? 0 : ( polyCorner1 + 1 ) );
847 
848  QPointF labelPos = tickEnd;
849 
850  qreal labelMargin = rulerAttr.labelMargin();
851  if ( labelMargin < 0 ) {
852  labelMargin = QFontMetricsF( tickLabel->realFont() ).height() * 0.5;
853  }
854  labelMargin -= tickLabel->marginWidth(); // make up for the margin that's already there
855 
856  switch ( position() ) {
857  case Left:
858  labelPos += QPointF( -size.width() - labelMargin,
859  -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
860  break;
861  case Right:
862  labelPos += QPointF( labelMargin,
863  -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
864  break;
865  case Top:
866  labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
867  -size.height() - labelMargin );
868  break;
869  case Bottom:
870  labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
871  labelMargin );
872  break;
873  }
874 
875  tickLabel->setGeometry( QRect( labelPos.toPoint(), size.toSize() ) );
876 
877  if ( step == Painting ) {
878  tickLabel->paint( painter );
879  }
880 
881  // collision check the current label against the previous one
882 
883  // like in the old code, we don't shorten or decimate labels if they are already the
884  // manual short type, or if they are the manual long type and on the vertical axis
885  // ### they can still collide though, especially when they're rotated!
886  if ( step == Layout ) {
887  int spaceSavingRotation = geoXy( 270, 0 );
888  bool canRotate = labelTA.autoRotate() && labelTA.rotation() != spaceSavingRotation;
889  const bool canShortenLabels = !geoXy.isY && it.type() == TickIterator::MajorTickManualLong &&
890  it.hasShorterLabels();
891  bool collides = false;
892  if ( it.type() == TickIterator::MajorTick || it.type() == TickIterator::MajorTickHeaderDataLabel
893  || canShortenLabels || canRotate ) {
894  if ( isFirstLabel ) {
895  isFirstLabel = false;
896  } else {
897  collides = tickLabel->intersects( *prevTickLabel, labelPos, prevTickLabelPos );
898  qSwap( prevTickLabel, tickLabel );
899  }
900  prevTickLabelPos = labelPos;
901  }
902  if ( collides ) {
903  // to make room, we try in order: shorten, rotate, decimate
904  if ( canRotate && !canShortenLabels ) {
905  labelTA.setRotation( spaceSavingRotation );
906  // tickLabel will be reused in the next round
907  tickLabel->setTextAttributes( labelTA );
908  } else {
909  labelThinningFactor++;
910  }
911  step--; // relayout
912  break;
913  }
914  }
915  }
916  }
917  delete tickLabel;
918  tickLabel = 0;
919  delete prevTickLabel;
920  prevTickLabel = 0;
921 
922  if ( ! titleText().isEmpty() ) {
923  d->drawTitleText( painter, plane, geometry() );
924  }
925 }
926 
927 /* pure virtual in QLayoutItem */
929 {
930  return false; // if the axis exists, it has some (perhaps default) content
931 }
932 
933 /* pure virtual in QLayoutItem */
934 Qt::Orientations CartesianAxis::expandingDirections() const
935 {
936  Qt::Orientations ret;
937  switch ( position() ) {
938  case Bottom:
939  case Top:
940  ret = Qt::Horizontal;
941  break;
942  case Left:
943  case Right:
944  ret = Qt::Vertical;
945  break;
946  default:
947  Q_ASSERT( false );
948  break;
949  };
950  return ret;
951 }
952 
954 {
955  d->cachedMaximumSize = QSize();
956 }
957 
958 /* pure virtual in QLayoutItem */
960 {
961  if ( ! d->cachedMaximumSize.isValid() )
962  d->cachedMaximumSize = d->calculateMaximumSize();
963  return d->cachedMaximumSize;
964 }
965 
966 QSize CartesianAxis::Private::calculateMaximumSize() const
967 {
968  if ( !diagram() ) {
969  return QSize();
970  }
971 
973  Q_ASSERT( plane );
974  QObject* refArea = plane->parent();
975  const bool centerTicks = referenceDiagramNeedsCenteredAbscissaTicks( diagram() )
976  && axis()->isAbscissa();
977 
978  // we ignore:
979  // - label thinning (expensive, not worst case and we want worst case)
980  // - label autorotation (expensive, obscure feature(?))
981  // - axis length (it is determined by the plane / diagram / chart anyway)
982  // - the title's influence on axis length; this one might be TODO. See KDCH-863.
983 
984  XySwitch geoXy( isVertical() );
985  qreal size = 0; // this is the size transverse to the axis direction
986 
987  // the following variables describe how much the first and last label stick out over the axis
988  // area, so that the geometry of surrounding layout items can be adjusted to make room.
989  qreal startOverhang = 0.0;
990  qreal endOverhang = 0.0;
991 
992  if ( mAxis->textAttributes().isVisible() ) {
993  // these four are used just to calculate startOverhang and endOverhang
994  qreal lowestLabelPosition = signalingNaN;
995  qreal highestLabelPosition = signalingNaN;
996  qreal lowestLabelLongitudinalSize = signalingNaN;
997  qreal highestLabelLongitudinalSize = signalingNaN;
998 
999  TextLayoutItem tickLabel( QString(), mAxis->textAttributes(), refArea,
1000  KDChartEnums::MeasureOrientationMinimum, Qt::AlignLeft );
1001  const RulerAttributes rulerAttr = mAxis->rulerAttributes();
1002 
1003  bool showFirstTick = rulerAttr.showFirstTick();
1004  for ( TickIterator it( axis(), plane, 1, centerTicks ); !it.isAtEnd(); ++it ) {
1005  const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
1006  if ( !showFirstTick ) {
1007  showFirstTick = true;
1008  continue;
1009  }
1010 
1011  qreal labelSizeTransverse = 0.0;
1012  qreal labelMargin = 0.0;
1013  QString text = it.text();
1014  if ( !text.isEmpty() ) {
1015  QPointF labelPosition = plane->translate( QPointF( geoXy( drawPos, (qreal)1.0 ),
1016  geoXy( (qreal)1.0, drawPos ) ) );
1017  highestLabelPosition = geoXy( labelPosition.x(), labelPosition.y() );
1018 
1019  if ( it.type() == TickIterator::MajorTick ) {
1020  // add unit prefixes and suffixes, then customize
1021  text = customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
1022  } else if ( it.type() == TickIterator::MajorTickHeaderDataLabel ) {
1023  // unit prefixes and suffixes have already been added in this case - only customize
1024  text = axis()->customizedLabel( text );
1025  }
1026  tickLabel.setText( text );
1027 
1028  QSize sz = tickLabel.sizeHint();
1029  highestLabelLongitudinalSize = geoXy( sz.width(), sz.height() );
1030  if ( ISNAN( lowestLabelLongitudinalSize ) ) {
1031  lowestLabelLongitudinalSize = highestLabelLongitudinalSize;
1032  lowestLabelPosition = highestLabelPosition;
1033  }
1034 
1035  labelSizeTransverse = geoXy( sz.height(), sz.width() );
1036  labelMargin = rulerAttr.labelMargin();
1037  if ( labelMargin < 0 ) {
1038  labelMargin = QFontMetricsF( tickLabel.realFont() ).height() * 0.5;
1039  }
1040  labelMargin -= tickLabel.marginWidth(); // make up for the margin that's already there
1041  }
1042  qreal tickLength = it.type() == TickIterator::CustomTick ?
1043  customTickLength : axis()->tickLength( it.type() == TickIterator::MinorTick );
1044  size = qMax( size, tickLength + labelMargin + labelSizeTransverse );
1045  }
1046 
1047  const DataDimension dimX = plane->gridDimensionsList().first();
1048  const DataDimension dimY = plane->gridDimensionsList().last();
1049 
1050  QPointF pt = plane->translate( QPointF( dimX.start, dimY.start ) );
1051  const qreal lowestPosition = geoXy( pt.x(), pt.y() );
1052  pt = plane->translate( QPointF( dimX.end, dimY.end ) );
1053  const qreal highestPosition = geoXy( pt.x(), pt.y() );
1054 
1055  // the geoXy( 1.0, -1.0 ) here is necessary because Qt's y coordinate is inverted
1056  startOverhang = qMax( 0.0, ( lowestPosition - lowestLabelPosition ) * geoXy( 1.0, -1.0 ) +
1057  lowestLabelLongitudinalSize * 0.5 );
1058  endOverhang = qMax( 0.0, ( highestLabelPosition - highestPosition ) * geoXy( 1.0, -1.0 ) +
1059  highestLabelLongitudinalSize * 0.5 );
1060  }
1061 
1062  amountOfLeftOverlap = geoXy( startOverhang, (qreal)0.0 );
1063  amountOfRightOverlap = geoXy( endOverhang, (qreal)0.0 );
1064  amountOfBottomOverlap = geoXy( (qreal)0.0, startOverhang );
1065  amountOfTopOverlap = geoXy( (qreal)0.0, endOverhang );
1066 
1067  const TextAttributes titleTA = titleTextAttributesWithAdjustedRotation();
1068  if ( titleTA.isVisible() && !axis()->titleText().isEmpty() ) {
1069  TextLayoutItem title( axis()->titleText(), titleTA, refArea, KDChartEnums::MeasureOrientationMinimum,
1070  Qt::AlignHCenter | Qt::AlignVCenter );
1071 
1072  QFontMetricsF titleFM( title.realFont(), GlobalMeasureScaling::paintDevice() );
1073  size += geoXy( titleFM.height() * 0.33, titleFM.averageCharWidth() * 0.55 ); // spacing
1074  size += geoXy( title.sizeHint().height(), title.sizeHint().width() );
1075  }
1076 
1077  // the size parallel to the axis direction is not determined by us, so we just return 1
1078  return QSize( geoXy( 1, int( size ) ), geoXy( int ( size ), 1 ) );
1079 }
1080 
1081 /* pure virtual in QLayoutItem */
1083 {
1084  return maximumSize();
1085 }
1086 
1087 /* pure virtual in QLayoutItem */
1089 {
1090  return maximumSize();
1091 }
1092 
1093 /* pure virtual in QLayoutItem */
1094 void CartesianAxis::setGeometry( const QRect& r )
1095 {
1096  if ( d->geometry != r ) {
1097  d->geometry = r;
1099  }
1100 }
1101 
1102 /* pure virtual in QLayoutItem */
1104 {
1105  return d->geometry;
1106 }
1107 
1109 {
1110  if ( d->customTickLength == value ) {
1111  return;
1112  }
1113  d->customTickLength = value;
1115  layoutPlanes();
1116 }
1117 
1119 {
1120  return d->customTickLength;
1121 }
1122 
1123 int CartesianAxis::tickLength( bool subUnitTicks ) const
1124 {
1125  const RulerAttributes& rulerAttr = rulerAttributes();
1126  return subUnitTicks ? rulerAttr.minorTickMarkLength() : rulerAttr.majorTickMarkLength();
1127 }
1128 
1130 {
1131  return d->annotations;
1132 }
1133 
1135 {
1136  if ( d->annotations == annotations )
1137  return;
1138 
1139  d->annotations = annotations;
1141  layoutPlanes();
1142 }
1143 
1145 {
1146  return d->customTicksPositions;
1147 }
1148 
1149 void CartesianAxis::setCustomTicks( const QList< qreal >& customTicksPositions )
1150 {
1151  if ( d->customTicksPositions == customTicksPositions )
1152  return;
1153 
1154  d->customTicksPositions = customTicksPositions;
1156  layoutPlanes();
1157 }
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
unsigned int autoAdjustVerticalRangeToData() const
Returns the maximal allowed percent of the vertical space covered by the coordinate plane that may be...
void setPainter(QPainter *painter)
void setAnnotations(const QMap< qreal, QString > &annotations)
Sets the axis annotations to annotations.
QSize maximumSize() const override
pure virtual in QLayoutItem
void setTitleTextAttributes(const TextAttributes &a)
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
AbstractCoordinatePlane * coordinatePlane() const
int rowCount(const QModelIndex &) const override
[reimplemented]
CartesianAxis(AbstractCartesianDiagram *diagram=0)
C&#39;tor of the class for cartesian axes.
Qt::Orientations expandingDirections() const override
pure virtual in QLayoutItem
TextAttributes titleTextAttributes() const
Returns the text attributes that will be used for displaying the title text.
virtual void setPosition(Position p)
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
void layoutPlanes()
Calling layoutPlanes() on the plane triggers the global KDChart::Chart::slotLayoutPlanes() ...
const GridAttributes gridAttributes(Qt::Orientation orientation) const
A set of attributes controlling the appearance of axis rulers.
QPainter * painter() const
void setTitleText(const QString &text)
Sets the optional text displayed as chart title.
void setCustomTickLength(int value)
Sets the length of custom ticks on the axis.
virtual AttributesModel * attributesModel() const
Returns the AttributesModel, that is used by this diagram.
Layout item showing a text.
unsigned int autoAdjustHorizontalRangeToData() const
Returns the maximal allowed percent of the horizontal space covered by the coordinate plane that may ...
AbstractCoordinatePlane * coordinatePlane() const
The coordinate plane associated with the diagram.
BarDiagram defines a common bar diagram.
static QPaintDevice * paintDevice()
Return the paint device to use for calculating font metrics.
QSize sizeHint() const override
pure virtual in QLayoutItem
void resetTitleTextAttributes()
Reset the title text attributes to the built-in default:
The base class for axes.
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
QString unitSuffix(int column, Qt::Orientation orientation, bool fallback=false) const
Retrieves the axis unit suffix for a specific column.
virtual bool intersects(const TextLayoutItem &other, const QPointF &myPos, const QPointF &otherPos) const
QString unitPrefix(int column, Qt::Orientation orientation, bool fallback=false) const
Retrieves the axis unit prefix for a specific column.
AbstractDiagram defines the interface for diagram classes.
QList< qreal > customTicks() const
Returns the currently set custom ticks on the axis.
void setText(const QString &text)
bool hasTickMarkPenAt(qreal value) const
const AbstractDiagram * diagram() const
void setTitleSize(qreal value)
use setTitleTextAttributes() instead
void setValue(qreal val)
void paintCtx(PaintContext *) override
reimpl
void setRotation(int rotation)
Set the rotation angle to use for the text.
The class for cartesian axes.
bool compare(const CartesianAxis *other) const
Returns true if both axes have the same settings.
void setCustomTicks(const QList< qreal > &ticksPostions)
Sets custom ticks on the axis.
void setFontSize(const Measure &measure)
Set the size of the font used for rendering text.
TextAttributes textAttributes() const
Returns the text attributes to be used for axis labels.
static QMap< qreal, QString > allAxisAnnotations(const AbstractCoordinatePlane *plane, bool isY)
virtual QFont realFont() const
A proxy model used for decorating data with attributes.
#define d
bool isEmpty() const override
pure virtual in QLayoutItem
LineDiagram defines a common line diagram.
virtual bool isOrdinate() const
virtual AbstractCartesianDiagram * referenceDiagram() const
virtual KDChart::CartesianAxisList axes() const
void setTextAttributes(const TextAttributes &a)
Use this to specify the text attributes to be used for this item.
virtual bool isAbscissa() const
virtual int tickLength(bool subUnitTicks=false) const
bool compare(const AbstractAxis *other) const
Returns true if both axes have the same settings.
QSize sizeHint() const override
pure virtual in QLayoutItem
void paint(QPainter *) override
reimpl
Base class for diagrams based on a cartesian coordianate system.
Stores information about painting diagrams.
virtual const Position position() const
QRect geometry() const override
pure virtual in QLayoutItem
QMap< qreal, QString > annotations() const
Returns the currently set axis annotations.
static bool referenceDiagramIsBarDiagram(const AbstractDiagram *diagram)
void paint(QPainter *) override
QSize minimumSize() const override
pure virtual in QLayoutItem
A set of attributes controlling the appearance of grids.
static bool referenceDiagramNeedsCenteredAbscissaTicks(const AbstractDiagram *diagram)
DataDimensionsList gridDimensionsList()
Returns the dimensions used for drawing the grid lines.
QStringList itemRowLabels() const
The set of item row labels currently displayed, for use in Abscissa axes, etc.
Measure is used to specify relative and absolute sizes in KDChart, e.g.
int customTickLength() const
Returns the length of custom ticks on the axis.
virtual int marginWidth() const
Class only listed here to document inheritance of some KDChart classes.
static int numSignificantDecimalPlaces(qreal floatNumber)
virtual const QString customizedLabel(const QString &label) const
Reimplement this method if you want to adjust axis labels before they are printed.
void setCoordinatePlane(AbstractCoordinatePlane *plane)
void setRectangle(const QRectF &rect)
Helper class for one dimension of data, e.g.
static qreal slightlyLessThan(qreal r)
bool hasDefaultTitleTextAttributes() const
virtual void takeAxis(CartesianAxis *axis)
Removes the axis from the diagram, without deleting it.
QPolygon boundingPolygon() const
RulerAttributes rulerAttributes() const
Returns the attributes to be used for painting the rulers.
A set of text attributes.
QRect areaGeometry() const override

Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/

https://www.kdab.com/development-resources/qt-tools/kd-chart/