24 #include "KDChartCartesianAxis_p.h"
32 #include <QApplication>
37 #include "KDChartAbstractDiagram_p.h"
38 #include "KDChartAbstractGrid.h"
39 #include "KDChartPainterSaver_p.h"
46 #include <KDABLibFakes>
56 return r - std::numeric_limits< qreal >::epsilon() * 1e-6;
59 qreal diff = qAbs( r ) * std::numeric_limits< qreal >::epsilon() * 2.0;
65 static const int maxPlaces = 15;
66 QString sample = QString::number( floatNumber,
'f', maxPlaces ).section( QLatin1Char(
'.'), 1, 2 );
68 for ( ; ret > 0; ret-- ) {
69 if ( sample[ ret - 1 ] != QLatin1Char(
'0' ) ) {
83 m_majorThinningFactor( majorThinningFactor ),
84 m_majorLabelCount( 0 ),
88 const CartesianAxis::Private *axisPriv = CartesianAxis::Private::get( a );
89 XySwitch xy( axisPriv->isVertical() );
94 m_dimension.end -= m_dimension.stepWidth;
97 m_annotations = axisPriv->annotations;
98 m_customTicks = axisPriv->customTicksPositions;
100 const qreal inf = std::numeric_limits< qreal >::infinity();
102 if ( m_customTicks.count() ) {
103 qSort( m_customTicks.begin(), m_customTicks.end() );
104 m_customTickIndex = 0;
105 m_customTick = m_customTicks.at( m_customTickIndex );
107 m_customTickIndex = -1;
111 if ( m_majorThinningFactor > 1 && hasShorterLabels() ) {
112 m_manualLabelTexts = m_axis->shortLabels();
114 m_manualLabelTexts = m_axis->labels();
116 m_manualLabelIndex = m_manualLabelTexts.isEmpty() ? -1 : 0;
118 if ( !m_dimension.isCalculated ) {
124 QStringList dataHeaderLabels;
127 if ( !dataHeaderLabels.isEmpty() ) {
129 const int anchorCount = model->
rowCount( QModelIndex() );
130 if ( anchorCount == dataHeaderLabels.count() ) {
131 for (
int i = 0; i < anchorCount; i++ ) {
133 m_dataHeaderLabels.insert( qreal( i ), dataHeaderLabels.at( i ) );
139 bool hasMajorTicks = m_axis->rulerAttributes().showMajorTickMarks();
140 bool hasMinorTicks = m_axis->rulerAttributes().showMinorTickMarks();
142 init( xy.isY, hasMajorTicks, hasMinorTicks, plane );
154 const CartesianAxis::Private *axisPriv = CartesianAxis::Private::get( axis );
155 if ( axisPriv->isVertical() == isY ) {
156 annotations.unite( axisPriv->annotations );
163 TickIterator::TickIterator(
bool isY,
const DataDimension& dimension,
bool useAnnotationsForTicks,
166 m_dimension( dimension ),
167 m_majorThinningFactor( 1 ),
168 m_majorLabelCount( 0 ),
169 m_customTickIndex( -1 ),
170 m_manualLabelIndex( -1 ),
172 m_customTick( std::numeric_limits< qreal >::infinity() )
174 if ( useAnnotationsForTicks ) {
177 init( isY, hasMajorTicks, hasMinorTicks, plane );
180 void TickIterator::init(
bool isY,
bool hasMajorTicks,
bool hasMinorTicks,
183 Q_ASSERT( std::numeric_limits< qreal >::has_infinity );
185 m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
187 hasMajorTicks = hasMajorTicks && ( m_dimension.stepWidth > 0 || m_isLogarithmic );
188 hasMinorTicks = hasMinorTicks && ( m_dimension.subStepWidth > 0 || m_isLogarithmic );
193 m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
194 if ( !m_isLogarithmic ) {
202 m_dimension = AbstractGrid::adjustedLowerUpperRange( m_dimension, adjustLower, adjustUpper );
207 m_decimalPlaces = -1;
210 const qreal inf = std::numeric_limits< qreal >::infinity();
214 if ( m_isLogarithmic ) {
215 if ( ISNAN( m_dimension.start ) || ISNAN( m_dimension.end ) ) {
218 m_dimension.start = 0.0;
219 m_dimension.end = 0.0;
223 }
else if ( m_dimension.start >= 0 ) {
224 m_position = m_dimension.start ? pow( 10.0, floor( log10( m_dimension.start ) ) - 1.0 )
226 m_majorTick = hasMajorTicks ? m_position : inf;
227 m_minorTick = hasMinorTicks ? m_position * 20.0 : inf;
229 m_position = -pow( 10.0, ceil( log10( -m_dimension.start ) ) + 1.0 );
230 m_majorTick = hasMajorTicks ? m_position : inf;
231 m_minorTick = hasMinorTicks ? m_position * 0.09 : inf;
234 m_majorTick = hasMajorTicks ? m_dimension.start : inf;
235 m_minorTick = hasMinorTicks ? m_dimension.start : inf;
242 bool TickIterator::areAlmostEqual( qreal r1, qreal r2 )
const
244 if ( !m_isLogarithmic ) {
245 qreal span = m_dimension.end - m_dimension.start;
249 span = qFuzzyIsNull( m_dimension.start) ? 1 : qAbs( m_dimension.start );
251 return qAbs( r2 - r1 ) < ( span ) * 1e-6;
253 return qAbs( r2 - r1 ) < qMax( qAbs( r1 ), qAbs( r2 ) ) * 0.01;
257 bool TickIterator::isHigherPrecedence( qreal importantTick, qreal unimportantTick )
const
259 return importantTick != std::numeric_limits< qreal >::infinity() &&
260 ( importantTick <= unimportantTick || areAlmostEqual( importantTick, unimportantTick ) );
263 void TickIterator::computeMajorTickLabel(
int decimalPlaces )
265 if ( m_manualLabelIndex >= 0 ) {
266 m_text = m_manualLabelTexts[ m_manualLabelIndex++ ];
267 if ( m_manualLabelIndex >= m_manualLabelTexts.count() ) {
269 m_manualLabelIndex = 0;
271 m_type = m_majorThinningFactor > 1 ? MajorTickManualShort : MajorTickManualLong;
274 if ( m_axis && ( m_majorLabelCount++ % m_majorThinningFactor ) == 0 ) {
278 if ( it != m_dataHeaderLabels.constEnd() && areAlmostEqual( it.key(), m_position ) ) {
280 m_type = MajorTickHeaderDataLabel;
283 if ( decimalPlaces < 0 ) {
286 m_text = QString::number( m_position,
'f', decimalPlaces );
296 void TickIterator::operator++()
301 const qreal inf = std::numeric_limits< qreal >::infinity();
305 if ( !m_annotations.isEmpty() ) {
307 if ( it != m_annotations.constEnd() ) {
308 m_position = it.key();
314 }
else if ( !m_isLogarithmic && m_dimension.stepWidth * 1e6 <
315 qMax( qAbs( m_dimension.start ), qAbs( m_dimension.end ) ) ) {
323 if ( m_isLogarithmic ) {
324 while ( m_majorTick <= m_position ) {
325 m_majorTick *= m_position >= 0 ? 10 : 0.1;
327 while ( m_minorTick <= m_position ) {
329 m_minorTick += m_majorTick * ( m_position >= 0 ? 0.1 : 1.0 );
332 while ( m_majorTick <= m_position ) {
333 m_majorTick += m_dimension.stepWidth;
335 while ( m_minorTick <= m_position ) {
336 m_minorTick += m_dimension.subStepWidth;
340 while ( m_customTickIndex >= 0 && m_customTick <= m_position ) {
341 if ( ++m_customTickIndex >= m_customTicks.count() ) {
342 m_customTickIndex = -1;
346 m_customTick = m_customTicks.at( m_customTickIndex );
350 if ( isHigherPrecedence( m_customTick, m_majorTick ) && isHigherPrecedence( m_customTick, m_minorTick ) ) {
351 m_position = m_customTick;
352 computeMajorTickLabel( -1 );
355 if ( m_type == MajorTick ) {
358 }
else if ( isHigherPrecedence( m_majorTick, m_minorTick ) ) {
359 m_position = m_majorTick;
360 if ( m_minorTick != inf ) {
362 m_minorTick = m_majorTick;
364 computeMajorTickLabel( m_decimalPlaces );
365 }
else if ( m_minorTick != inf ) {
366 m_position = m_minorTick;
374 if ( m_position > m_dimension.end || ISNAN( m_position ) ) {
382 :
AbstractAxis ( new Private( diagram, this ), diagram )
391 while (
d->mDiagram ) {
401 void CartesianAxis::init()
403 d->customTickLength = 3;
406 connect(
this, SIGNAL( coordinateSystemChanged() ), SLOT( coordinateSystemChanged() ) );
412 if ( other ==
this ) {
423 void CartesianAxis::coordinateSystemChanged()
442 d->titleTextAttributes = a;
443 d->useDefaultTextAttributes =
false;
457 return d->titleTextAttributes;
462 d->useDefaultTextAttributes =
true;
469 return d->useDefaultTextAttributes;
474 if (
d->position == p ) {
485 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(Q_COMPILER_MANGLES_RETURN_TYPE)
495 if ( !
d->diagram() || !
d->diagram()->coordinatePlane() ) {
507 qobject_cast< const AbstractCartesianDiagram * >( diagram );
510 return qobject_cast< const BarDiagram* >( dia ) != 0;
516 qobject_cast< const AbstractCartesianDiagram * >( diagram );
519 if ( qobject_cast< const BarDiagram* >( dia ) )
521 if ( qobject_cast< const StockDiagram* >( dia ) )
524 const LineDiagram * lineDiagram = qobject_cast< const LineDiagram* >( dia );
543 if ( !
d->diagram() || !
d->diagram()->coordinatePlane() ) {
552 PainterSaver painterSaver( painter );
557 if ( zoomFactor > 1.0 ) {
558 painter->setClipRegion(
areaGeometry().adjusted( -
d->amountOfLeftOverlap - 1, -
d->amountOfTopOverlap - 1,
559 d->amountOfRightOverlap + 1,
d->amountOfBottomOverlap + 1 ) );
564 const TextAttributes CartesianAxis::Private::titleTextAttributesWithAdjustedRotation()
const
567 int rotation = titleTA.rotation();
568 if ( position == Left || position == Right ) {
571 if ( rotation >= 360 ) {
575 rotation = ( rotation / 90 ) * 90;
576 titleTA.setRotation( rotation );
580 QString CartesianAxis::Private::customizedLabelText(
const QString& text, Qt::Orientation orientation,
584 QString withUnits = diagram()->unitPrefix(
int( value ), orientation,
true ) +
586 diagram()->unitSuffix(
int( value ), orientation,
true );
587 return axis()->customizedLabel( withUnits );
592 d->axisTitleSpace = axisTitleSpace;
597 return d->axisTitleSpace;
613 const QRect& geoRect )
const
615 const TextAttributes titleTA( titleTextAttributesWithAdjustedRotation() );
616 if ( titleTA.isVisible() ) {
618 Qt::AlignHCenter | Qt::AlignVCenter );
620 QSize size = titleItem.sizeHint();
621 switch ( position ) {
623 point.setX( geoRect.left() + geoRect.width() / 2 );
624 point.setY( geoRect.top() + ( size.height() / 2 ) / axisTitleSpace );
625 size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
628 point.setX( geoRect.left() + geoRect.width() / 2 );
629 point.setY( geoRect.bottom() - ( size.height() / 2 ) / axisTitleSpace );
630 size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
633 point.setX( geoRect.left() + ( size.width() / 2 ) / axisTitleSpace );
634 point.setY( geoRect.top() + geoRect.height() / 2 );
635 size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
638 point.setX( geoRect.right() - ( size.width() / 2 ) / axisTitleSpace );
639 point.setY( geoRect.top() + geoRect.height() / 2 );
640 size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
643 const PainterSaver painterSaver( painter );
644 painter->setClipping(
false );
645 painter->translate( point );
646 titleItem.setGeometry( QRect( QPoint( -size.width() / 2, -size.height() / 2 ), size ) );
647 titleItem.paint( painter );
651 bool CartesianAxis::Private::isVertical()
const
653 return axis()->isAbscissa() == AbstractDiagram::Private::get( diagram() )->isTransposed();
658 Q_ASSERT_X (
d->diagram(),
"CartesianAxis::paint",
659 "Function call not allowed: The axis is not assigned to any diagram." );
662 Q_ASSERT_X ( plane,
"CartesianAxis::paint",
663 "Bad function call: PaintContext::coordinatePlane() NOT a cartesian plane." );
667 if ( !
d->diagram()->model() ) {
673 XySwitch geoXy(
d->isVertical() );
675 QPainter*
const painter = context->
painter();
679 qreal transversePosition = signalingNaN;
682 qreal transverseScreenSpaceShift = signalingNaN;
689 QPointF end( dimX.
end, dimY.
end );
694 end.setY( dimY.
start );
697 start.setY( dimY.
end );
700 end.setX( dimX.
start );
703 start.setX( dimX.
end );
707 transversePosition = geoXy( start.y(), start.x() );
709 QPointF transStart = plane->
translate( start );
710 QPointF transEnd = plane->
translate( end );
718 transverseScreenSpaceShift = geo.top() - transStart.y();
721 transverseScreenSpaceShift = geo.bottom() - transStart.y();
724 transverseScreenSpaceShift = geo.right() - transStart.x();
727 transverseScreenSpaceShift = geo.left() - transStart.x();
731 geoXy.lvalue( transStart.ry(), transStart.rx() ) += transverseScreenSpaceShift;
732 geoXy.lvalue( transEnd.ry(), transEnd.rx() ) += transverseScreenSpaceShift;
735 bool clipSaved = context->
painter()->hasClipping();
736 painter->setClipping(
false );
737 painter->drawLine( transStart, transEnd );
738 painter->setClipping( clipSaved );
747 int labelThinningFactor = 1;
753 QPointF prevTickLabelPos;
759 for (
int step = labelTA.
isVisible() ? Layout : Painting; step < Done; step++ ) {
761 bool isFirstLabel =
true;
762 for ( TickIterator it(
this, plane, labelThinningFactor, centerTicks ); !it.isAtEnd(); ++it ) {
763 if ( skipFirstTick ) {
764 skipFirstTick =
false;
768 const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
769 QPointF onAxis = plane->
translate( geoXy( QPointF( drawPos, transversePosition ) ,
770 QPointF( transversePosition, drawPos ) ) );
771 geoXy.lvalue( onAxis.ry(), onAxis.rx() ) += transverseScreenSpaceShift;
776 QPointF tickEnd = onAxis;
777 qreal tickLen = it.type() == TickIterator::CustomTick ?
778 d->customTickLength :
tickLength( it.type() == TickIterator::MinorTick );
779 geoXy.lvalue( tickEnd.ry(), tickEnd.rx() ) += isOutwardsPositive ? tickLen : -tickLen;
789 if ( step == Painting ) {
792 painter->setPen( rulerAttr.
tickMarkPen( it.position() ) );
794 painter->setPen( it.type() == TickIterator::MinorTick ? rulerAttr.
minorTickMarkPen()
797 painter->drawLine( onAxis, tickEnd );
801 if ( it.text().isEmpty() || !labelTA.
isVisible() ) {
808 QString text = it.text();
809 if ( it.type() == TickIterator::MajorTick ) {
811 text =
d->customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
812 }
else if ( it.type() == TickIterator::MajorTickHeaderDataLabel ) {
818 QSizeF size = QSizeF( tickLabel->
sizeHint() );
820 Q_ASSERT( labelPoly.count() == 4 );
827 axisAngle = 0;
break;
829 axisAngle = 180;
break;
831 axisAngle = 270;
break;
833 axisAngle = 90;
break;
840 int relAngle = axisAngle - labelTA.
rotation() + 45;
841 if ( relAngle < 0 ) {
844 int polyCorner1 = relAngle / 90;
845 QPoint p1 = labelPoly.at( polyCorner1 );
846 QPoint p2 = labelPoly.at( polyCorner1 == 3 ? 0 : ( polyCorner1 + 1 ) );
848 QPointF labelPos = tickEnd;
851 if ( labelMargin < 0 ) {
852 labelMargin = QFontMetricsF( tickLabel->
realFont() ).height() * 0.5;
858 labelPos += QPointF( -size.width() - labelMargin,
859 -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
862 labelPos += QPointF( labelMargin,
863 -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
866 labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
867 -size.height() - labelMargin );
870 labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
875 tickLabel->
setGeometry( QRect( labelPos.toPoint(), size.toSize() ) );
877 if ( step == Painting ) {
878 tickLabel->
paint( painter );
886 if ( step == Layout ) {
887 int spaceSavingRotation = geoXy( 270, 0 );
889 const bool canShortenLabels = !geoXy.isY && it.type() == TickIterator::MajorTickManualLong &&
890 it.hasShorterLabels();
891 bool collides =
false;
892 if ( it.type() == TickIterator::MajorTick || it.type() == TickIterator::MajorTickHeaderDataLabel
893 || canShortenLabels || canRotate ) {
894 if ( isFirstLabel ) {
895 isFirstLabel =
false;
897 collides = tickLabel->
intersects( *prevTickLabel, labelPos, prevTickLabelPos );
898 qSwap( prevTickLabel, tickLabel );
900 prevTickLabelPos = labelPos;
904 if ( canRotate && !canShortenLabels ) {
909 labelThinningFactor++;
919 delete prevTickLabel;
923 d->drawTitleText( painter, plane,
geometry() );
936 Qt::Orientations ret;
940 ret = Qt::Horizontal;
955 d->cachedMaximumSize = QSize();
961 if ( !
d->cachedMaximumSize.isValid() )
962 d->cachedMaximumSize =
d->calculateMaximumSize();
963 return d->cachedMaximumSize;
966 QSize CartesianAxis::Private::calculateMaximumSize()
const
976 && axis()->isAbscissa();
984 XySwitch geoXy( isVertical() );
989 qreal startOverhang = 0.0;
990 qreal endOverhang = 0.0;
992 if ( mAxis->textAttributes().isVisible() ) {
994 qreal lowestLabelPosition = signalingNaN;
995 qreal highestLabelPosition = signalingNaN;
996 qreal lowestLabelLongitudinalSize = signalingNaN;
997 qreal highestLabelLongitudinalSize = signalingNaN;
999 TextLayoutItem tickLabel( QString(), mAxis->textAttributes(), refArea,
1004 for ( TickIterator it( axis(), plane, 1, centerTicks ); !it.isAtEnd(); ++it ) {
1005 const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
1006 if ( !showFirstTick ) {
1007 showFirstTick =
true;
1011 qreal labelSizeTransverse = 0.0;
1012 qreal labelMargin = 0.0;
1013 QString text = it.text();
1014 if ( !text.isEmpty() ) {
1015 QPointF labelPosition = plane->
translate( QPointF( geoXy( drawPos, (qreal)1.0 ),
1016 geoXy( (qreal)1.0, drawPos ) ) );
1017 highestLabelPosition = geoXy( labelPosition.x(), labelPosition.y() );
1019 if ( it.type() == TickIterator::MajorTick ) {
1021 text = customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
1022 }
else if ( it.type() == TickIterator::MajorTickHeaderDataLabel ) {
1024 text = axis()->customizedLabel( text );
1026 tickLabel.setText( text );
1028 QSize sz = tickLabel.sizeHint();
1029 highestLabelLongitudinalSize = geoXy( sz.width(), sz.height() );
1030 if ( ISNAN( lowestLabelLongitudinalSize ) ) {
1031 lowestLabelLongitudinalSize = highestLabelLongitudinalSize;
1032 lowestLabelPosition = highestLabelPosition;
1035 labelSizeTransverse = geoXy( sz.height(), sz.width() );
1037 if ( labelMargin < 0 ) {
1038 labelMargin = QFontMetricsF( tickLabel.realFont() ).height() * 0.5;
1040 labelMargin -= tickLabel.marginWidth();
1042 qreal tickLength = it.type() == TickIterator::CustomTick ?
1043 customTickLength : axis()->tickLength( it.type() == TickIterator::MinorTick );
1044 size = qMax( size, tickLength + labelMargin + labelSizeTransverse );
1051 const qreal lowestPosition = geoXy( pt.x(), pt.y() );
1053 const qreal highestPosition = geoXy( pt.x(), pt.y() );
1056 startOverhang = qMax( 0.0, ( lowestPosition - lowestLabelPosition ) * geoXy( 1.0, -1.0 ) +
1057 lowestLabelLongitudinalSize * 0.5 );
1058 endOverhang = qMax( 0.0, ( highestLabelPosition - highestPosition ) * geoXy( 1.0, -1.0 ) +
1059 highestLabelLongitudinalSize * 0.5 );
1062 amountOfLeftOverlap = geoXy( startOverhang, (qreal)0.0 );
1063 amountOfRightOverlap = geoXy( endOverhang, (qreal)0.0 );
1064 amountOfBottomOverlap = geoXy( (qreal)0.0, startOverhang );
1065 amountOfTopOverlap = geoXy( (qreal)0.0, endOverhang );
1067 const TextAttributes titleTA = titleTextAttributesWithAdjustedRotation();
1068 if ( titleTA.
isVisible() && !axis()->titleText().isEmpty() ) {
1070 Qt::AlignHCenter | Qt::AlignVCenter );
1073 size += geoXy( titleFM.height() * 0.33, titleFM.averageCharWidth() * 0.55 );
1074 size += geoXy( title.sizeHint().height(), title.sizeHint().width() );
1078 return QSize( geoXy( 1,
int( size ) ), geoXy(
int ( size ), 1 ) );
1096 if (
d->geometry != r ) {
1110 if (
d->customTickLength == value ) {
1113 d->customTickLength = value;
1120 return d->customTickLength;
1131 return d->annotations;
1146 return d->customTicksPositions;
1151 if (
d->customTicksPositions == customTicksPositions )
1154 d->customTicksPositions = customTicksPositions;