KChart

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

KDE's Doxygen guidelines are available online.