KChart

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

KDE's Doxygen guidelines are available online.