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
32using namespace KChart;
33
34#define d (d_func())
35
36static 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
47static 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
65template<typename T>
66static const T& constify(T &v)
67{
68 return v;
69}
70
71TickIterator::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() ) {
119 AttributesModel* model = dia->attributesModel();
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
136static 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
156TickIterator::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
173void TickIterator::init( bool isY, bool hasMajorTicks, bool hasMinorTicks,
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
235bool 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
250bool TickIterator::isHigherPrecedence( qreal importantTick, qreal unimportantTick ) const
251{
252 return importantTick != std::numeric_limits< qreal >::infinity() &&
253 ( importantTick <= unimportantTick || areAlmostEqual( importantTick, unimportantTick ) );
254}
255
256void 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
289void 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
380CartesianAxis::~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 ) {
386 cd->takeAxis( this );
387 }
388 for ( AbstractDiagram *diagram : qAsConst(d->secondaryDiagrams) ) {
390 cd->takeAxis( this );
391 }
392}
393
394void CartesianAxis::init()
395{
396 d->customTickLength = 3;
397 d->position = Bottom;
398 setCachedSizeDirty();
399 connect( this, SIGNAL(coordinateSystemChanged()), SLOT(slotCoordinateSystemChanged()) );
400}
401
402
403bool 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
416void CartesianAxis::slotCoordinateSystemChanged()
417{
418 layoutPlanes();
419}
420
422{
423 d->titleText = text;
424 setCachedSizeDirty();
425 layoutPlanes();
426}
427
428QString CartesianAxis::titleText() const
429{
430 return d->titleText;
431}
432
433void 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
460bool CartesianAxis::hasDefaultTitleTextAttributes() const
461{
462 return d->useDefaultTextAttributes;
463}
464
465void 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)
479const
480#endif
481CartesianAxis::Position CartesianAxis::position() const
482{
483 return d->position;
484}
485
486void 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
497static 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
506static 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
521bool 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
529bool CartesianAxis::isOrdinate() const
530{
531 return !isAbscissa();
532}
533
535{
536 if ( !d->diagram() || !d->diagram()->coordinatePlane() ) {
537 return;
538 }
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
557const 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
573QString 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
583void CartesianAxis::setTitleSpace( qreal axisTitleSpace )
584{
585 d->axisTitleSpace = axisTitleSpace;
586}
587
589{
590 return d->axisTitleSpace;
591}
592
594{
595 Q_UNUSED( value )
596 // ### remove me
597}
598
600{
601 // ### remove me
602 return 1.0;
603}
604
605void 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
644bool 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.
676 {
677 // determine the unadulterated position in screen space
678
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
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
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 );
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. );
765 geoXy.lvalue( onAxis.ry(), onAxis.rx() ) += transverseScreenSpaceShift;
766 const bool isOutwardsPositive = position() == Bottom || position() == Right;
767
768 // paint the tick mark
769
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
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
888 if ( isFirstLabel ) {
889 isFirstLabel = false;
890 } else {
891 collides = tickLabel->intersects( *prevTickLabel, labelPos, prevTickLabelPos );
892 qSwap( prevTickLabel, tickLabel );
893 }
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 {
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{
931 switch ( position() ) {
932 case Bottom:
933 case Top:
935 break;
936 case Left:
937 case Right:
939 break;
940 default:
941 Q_ASSERT( false );
942 break;
943 };
944 return ret;
945}
946
947void 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
960QSize 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
1117int CartesianAxis::tickLength( bool subUnitTicks ) const
1118{
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
1143void 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)
1154QDebug 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
QRect areaGeometry() const override
The base class for axes.
RulerAttributes rulerAttributes() const
Returns the attributes to be used for painting the rulers.
virtual const QString customizedLabel(const QString &label) const
Reimplement this method if you want to adjust axis labels before they are printed.
bool compare(const AbstractAxis *other) const
Returns true if both axes have the same settings.
TextAttributes textAttributes() const
Returns the text attributes to be used for axis labels.
Base class for diagrams based on a cartesian coordianate system.
virtual KChart::CartesianAxisList axes() const
virtual AbstractCartesianDiagram * referenceDiagram() const
virtual void takeAxis(CartesianAxis *axis)
Removes the axis from the diagram, without deleting it.
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
void layoutPlanes()
Calling layoutPlanes() on the plane triggers the global KChart::Chart::slotLayoutPlanes()
DataDimensionsList gridDimensionsList()
Returns the dimensions used for drawing the grid lines.
AbstractDiagram defines the interface for diagram classes.
virtual AttributesModel * attributesModel() const
Returns the AttributesModel, that is used by this diagram.
QString unitPrefix(int column, Qt::Orientation orientation, bool fallback=false) const
Retrieves the axis unit prefix for a specific column.
AbstractCoordinatePlane * coordinatePlane() const
The coordinate plane associated with the diagram.
QStringList itemRowLabels() const
The set of item row labels currently displayed, for use in Abscissa axes, etc.
QString unitSuffix(int column, Qt::Orientation orientation, bool fallback=false) const
Retrieves the axis unit suffix for a specific column.
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.
A proxy model used for decorating data with attributes.
int rowCount(const QModelIndex &) const override
\reimpl
BarDiagram defines a common bar diagram.
The class for cartesian axes.
void resetTitleTextAttributes()
Reset the title text attributes to the built-in default:
bool isEmpty() const override
pure virtual in QLayoutItem
void setCustomTickLength(int value)
Sets the length of custom ticks on the axis.
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
QSize maximumSize() const override
pure virtual in QLayoutItem
QList< qreal > customTicks() const
Returns the currently set custom ticks on the axis.
QMap< qreal, QString > annotations() const
Returns the currently set axis annotations.
CartesianAxis(AbstractCartesianDiagram *diagram=nullptr)
C'tor of the class for cartesian axes.
int customTickLength() const
Returns the length of custom ticks on the axis.
void paintCtx(PaintContext *) override
reimpl
QSize sizeHint() const override
pure virtual in QLayoutItem
QSize minimumSize() const override
pure virtual in QLayoutItem
Qt::Orientations expandingDirections() const override
pure virtual in QLayoutItem
bool compare(const CartesianAxis *other) const
Returns true if both axes have the same settings.
QRect geometry() const override
pure virtual in QLayoutItem
TextAttributes titleTextAttributes() const
Returns the text attributes that will be used for displaying the title text.
void paint(QPainter *) override
reimpl
void setTitleText(const QString &text)
Sets the optional text displayed as axis title.
void setCustomTicks(const QList< qreal > &ticksPostions)
Sets custom ticks on the axis.
void setAnnotations(const QMap< qreal, QString > &annotations)
Sets the axis annotations to annotations.
void setTitleSpace(qreal value)
void setTitleSize(qreal value)
use setTitleTextAttributes() instead
unsigned int autoAdjustVerticalRangeToData() const
Returns the maximal allowed percent of the vertical space covered by the coordinate plane that may be...
unsigned int autoAdjustHorizontalRangeToData() const
Returns the maximal allowed percent of the horizontal space covered by the coordinate plane that may ...
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
const GridAttributes gridAttributes(Qt::Orientation orientation) const
Helper class for one dimension of data, e.g.
static QPaintDevice * paintDevice()
Return the paint device to use for calculating font metrics.
A set of attributes controlling the appearance of grids.
LineDiagram defines a common line diagram.
Measure is used to specify relative and absolute sizes in KChart, e.g.
Stores information about painting diagrams.
Defines a position, using compass terminology.
A set of attributes controlling the appearance of axis rulers.
A set of text attributes.
Layout item showing a text.
QSize sizeHint() const override
pure virtual in QLayoutItem
void setTextAttributes(const TextAttributes &a)
Use this to specify the text attributes to be used for this item.
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
Q_SCRIPTABLE Q_NOREPLY void start()
int decimalPlaces(const int rangeMax, const int significantFigures)
QCA_EXPORT void init()
qreal height() const const
const_reference at(qsizetype i) const const
qsizetype count() const const
T & first()
bool isEmpty() const const
T & last()
iterator insert(const Key &key, const T &value)
Key key(const T &value, const Key &defaultKey) const const
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
void drawLine(const QLine &line)
void restore()
void save()
void setClipRegion(const QRegion &region, Qt::ClipOperation operation)
void setClipping(bool enable)
void setPen(Qt::PenStyle style)
void translate(const QPoint &offset)
int x() const const
int y() const const
void setX(qreal x)
void setY(qreal y)
qreal x() const const
qreal y() const const
int bottom() const const
int height() const const
int left() const const
int right() const const
int top() const const
int width() const const
int height() const const
void setHeight(int height)
void setWidth(int width)
int width() const const
qreal height() const const
QSize toSize() const const
qreal width() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
AlignHCenter
Horizontal
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:24 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.