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>
48 using namespace KDChart;
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' ) ) {
79 m_majorThinningFactor( majorThinningFactor ),
80 m_majorLabelCount( 0 ),
84 XySwitch xy( m_axis->d_func()->isVertical() );
89 m_dimension.end -= m_dimension.stepWidth;
92 m_annotations = m_axis->d_func()->annotations;
93 m_customTicks = m_axis->d_func()->customTicksPositions;
95 const qreal inf = std::numeric_limits< qreal >::infinity();
97 if ( m_customTicks.count() ) {
98 qSort( m_customTicks.begin(), m_customTicks.end() );
99 m_customTickIndex = 0;
100 m_customTick = m_customTicks.at( m_customTickIndex );
102 m_customTickIndex = -1;
106 if ( m_majorThinningFactor > 1 && hasShorterLabels() ) {
107 m_manualLabelTexts = m_axis->shortLabels();
109 m_manualLabelTexts = m_axis->labels();
111 m_manualLabelIndex = m_manualLabelTexts.isEmpty() ? -1 : 0;
113 if ( !m_dimension.isCalculated ) {
119 QStringList dataHeaderLabels;
122 if ( !dataHeaderLabels.isEmpty() ) {
124 const int anchorCount = model->
rowCount( QModelIndex() );
125 if ( anchorCount == dataHeaderLabels.count() ) {
126 for (
int i = 0; i < anchorCount; i++ ) {
128 m_dataHeaderLabels.insert( qreal( i ), dataHeaderLabels.at( i ) );
134 bool hasMajorTicks = m_axis->rulerAttributes().showMajorTickMarks();
135 bool hasMinorTicks = m_axis->rulerAttributes().showMinorTickMarks();
137 init( xy.isY, hasMajorTicks, hasMinorTicks, plane );
140 TickIterator::TickIterator(
bool isY,
const DataDimension& dimension,
bool hasMajorTicks,
bool hasMinorTicks,
143 m_dimension( dimension ),
144 m_majorThinningFactor( majorThinningFactor ),
145 m_majorLabelCount( 0 ),
146 m_customTickIndex( -1 ),
147 m_manualLabelIndex( -1 ),
149 m_customTick( std::numeric_limits< qreal >::infinity() )
151 init( isY, hasMajorTicks, hasMinorTicks, plane );
154 void TickIterator::init(
bool isY,
bool hasMajorTicks,
bool hasMinorTicks,
157 Q_ASSERT( std::numeric_limits< qreal >::has_infinity );
159 m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
161 hasMajorTicks = hasMajorTicks && ( m_dimension.stepWidth > 0 || m_isLogarithmic );
162 hasMinorTicks = hasMinorTicks && ( m_dimension.subStepWidth > 0 || m_isLogarithmic );
167 m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
168 if ( !m_isLogarithmic ) {
176 m_dimension = AbstractGrid::adjustedLowerUpperRange( m_dimension, adjustLower, adjustUpper );
181 m_decimalPlaces = -1;
184 const qreal inf = std::numeric_limits< qreal >::infinity();
188 if ( m_isLogarithmic ) {
189 if ( ISNAN( m_dimension.start ) || ISNAN( m_dimension.end ) ) {
192 m_dimension.start = 0.0;
193 m_dimension.end = 0.0;
197 }
else if ( m_dimension.start >= 0 ) {
198 m_position = m_dimension.start ? pow( 10.0, floor( log10( m_dimension.start ) ) - 1.0 )
200 m_majorTick = hasMajorTicks ? m_position : inf;
201 m_minorTick = hasMinorTicks ? m_position * 20.0 : inf;
203 m_position = -pow( 10.0, ceil( log10( -m_dimension.start ) ) + 1.0 );
204 m_majorTick = hasMajorTicks ? m_position : inf;
205 m_minorTick = hasMinorTicks ? m_position * 0.09 : inf;
208 m_majorTick = hasMajorTicks ? m_dimension.start : inf;
209 m_minorTick = hasMinorTicks ? m_dimension.start : inf;
216 bool TickIterator::areAlmostEqual( qreal r1, qreal r2 )
const
218 if ( !m_isLogarithmic ) {
219 return qAbs( r2 - r1 ) < ( m_dimension.end - m_dimension.start ) * 1e-6;
221 return qAbs( r2 - r1 ) < qMax( qAbs( r1 ), qAbs( r2 ) ) * 0.01;
225 bool TickIterator::isHigherPrecedence( qreal importantTick, qreal unimportantTick )
const
227 return importantTick != std::numeric_limits< qreal >::infinity() &&
228 ( importantTick <= unimportantTick || areAlmostEqual( importantTick, unimportantTick ) );
231 void TickIterator::computeMajorTickLabel(
int decimalPlaces )
233 if ( m_manualLabelIndex >= 0 ) {
234 m_text = m_manualLabelTexts[ m_manualLabelIndex++ ];
235 if ( m_manualLabelIndex >= m_manualLabelTexts.count() ) {
237 m_manualLabelIndex = 0;
239 m_type = m_majorThinningFactor > 1 ? MajorTickManualShort : MajorTickManualLong;
242 if ( m_axis && ( m_majorLabelCount++ % m_majorThinningFactor ) == 0 ) {
246 if ( it != m_dataHeaderLabels.constEnd() && areAlmostEqual( it.key(), m_position ) ) {
248 m_type = MajorTickHeaderDataLabel;
251 if ( decimalPlaces < 0 ) {
254 m_text = QString::number( m_position,
'f', decimalPlaces );
264 void TickIterator::operator++()
269 const qreal inf = std::numeric_limits< qreal >::infinity();
273 if ( !m_annotations.isEmpty() ) {
275 if ( it != m_annotations.constEnd() ) {
276 m_position = it.key();
282 }
else if (m_dimension.start == m_dimension.end) {
289 if ( m_isLogarithmic ) {
290 while ( m_majorTick <= m_position ) {
291 m_majorTick *= m_position >= 0 ? 10 : 0.1;
293 while ( m_minorTick <= m_position ) {
295 m_minorTick += m_majorTick * ( m_position >= 0 ? 0.1 : 1.0 );
298 while ( m_majorTick <= m_position ) {
299 m_majorTick += m_dimension.stepWidth;
301 while ( m_minorTick <= m_position ) {
302 m_minorTick += m_dimension.subStepWidth;
306 while ( m_customTickIndex >= 0 && m_customTick <= m_position ) {
307 if ( ++m_customTickIndex >= m_customTicks.count() ) {
308 m_customTickIndex = -1;
312 m_customTick = m_customTicks.at( m_customTickIndex );
316 if ( isHigherPrecedence( m_customTick, m_majorTick ) && isHigherPrecedence( m_customTick, m_minorTick ) ) {
317 m_position = m_customTick;
318 computeMajorTickLabel( -1 );
321 if ( m_type == MajorTick ) {
324 }
else if ( isHigherPrecedence( m_majorTick, m_minorTick ) ) {
325 m_position = m_majorTick;
326 if ( m_minorTick != inf ) {
328 m_minorTick = m_majorTick;
330 computeMajorTickLabel( m_decimalPlaces );
331 }
else if ( m_minorTick != inf ) {
332 m_position = m_minorTick;
340 if ( m_position > m_dimension.end || ISNAN( m_position ) ) {
348 :
AbstractAxis ( new Private( diagram, this ), diagram )
357 while (
d->mDiagram ) {
367 void CartesianAxis::init()
369 d->customTickLength = 3;
372 connect(
this, SIGNAL( coordinateSystemChanged() ), SLOT( coordinateSystemChanged() ) );
378 if ( other ==
this ) {
389 void CartesianAxis::coordinateSystemChanged()
408 d->titleTextAttributes = a;
409 d->useDefaultTextAttributes =
false;
422 return d->titleTextAttributes;
427 d->useDefaultTextAttributes =
true;
433 return d->useDefaultTextAttributes;
443 #if QT_VERSION < 0x040400 || defined(Q_COMPILER_MANGLES_RETURN_TYPE)
453 if ( !
d->diagram() || !
d->diagram()->coordinatePlane() ) {
468 return qobject_cast<
const BarDiagram* >( dia ) != 0;
477 if ( qobject_cast< const BarDiagram* >( dia ) )
479 if ( qobject_cast< const StockDiagram* >( dia ) )
501 if ( !
d->diagram() || !
d->diagram()->coordinatePlane() ) {
510 PainterSaver painterSaver( painter );
515 if ( zoomFactor > 1.0 ) {
516 painter->setClipRegion(
areaGeometry().adjusted( -
d->amountOfLeftOverlap - 1, -
d->amountOfTopOverlap - 1,
517 d->amountOfRightOverlap + 1,
d->amountOfBottomOverlap + 1 ) );
522 const TextAttributes CartesianAxis::Private::titleTextAttributesWithAdjustedRotation()
const
525 int rotation = titleTA.rotation();
526 if ( position == Left || position == Right ) {
529 if ( rotation >= 360 ) {
533 rotation = ( rotation / 90 ) * 90;
534 titleTA.setRotation( rotation );
538 QString CartesianAxis::Private::customizedLabelText(
const QString& text, Qt::Orientation orientation,
542 QString withUnits = diagram()->unitPrefix(
int( value ), orientation,
true ) +
544 diagram()->unitSuffix(
int( value ), orientation,
true );
545 return axis()->customizedLabel( withUnits );
550 d->axisTitleSpace = axisTitleSpace;
555 return d->axisTitleSpace;
569 const QRect& geoRect )
const
571 const TextAttributes titleTA( titleTextAttributesWithAdjustedRotation() );
572 if ( titleTA.isVisible() ) {
574 Qt::AlignHCenter | Qt::AlignVCenter );
579 switch ( position ) {
581 point.setX( geoRect.left() + geoRect.width() / 2 );
582 point.setY( geoRect.top() + ( size.height() / 2 ) / axisTitleSpace );
583 size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
586 point.setX( geoRect.left() + geoRect.width() / 2 );
587 point.setY( geoRect.bottom() - ( size.height() / 2 ) / axisTitleSpace );
588 size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
591 point.setX( geoRect.left() + ( size.width() / 2 ) / axisTitleSpace );
592 point.setY( geoRect.top() + geoRect.height() / 2 );
593 size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
596 point.setX( geoRect.right() - ( size.width() / 2 ) / axisTitleSpace );
597 point.setY( geoRect.top() + geoRect.height() / 2 );
598 size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
601 const PainterSaver painterSaver( painter );
602 painter->translate( point );
603 titleItem.setGeometry( QRect( QPoint( -size.width() / 2, -size.height() / 2 ), size ) );
604 titleItem.paint( painter );
608 bool CartesianAxis::Private::isVertical()
const
610 return axis()->isAbscissa() == AbstractDiagram::Private::get( diagram() )->isTransposed();
615 Q_ASSERT_X (
d->diagram(),
"CartesianAxis::paint",
616 "Function call not allowed: The axis is not assigned to any diagram." );
619 Q_ASSERT_X ( plane,
"CartesianAxis::paint",
620 "Bad function call: PaintContext::coordinatePlane() NOT a cartesian plane." );
624 if ( !
d->diagram()->model() ) {
630 XySwitch geoXy(
d->isVertical() );
632 QPainter*
const painter = context->
painter();
636 qreal transversePosition = signalingNaN;
639 qreal transverseScreenSpaceShift = signalingNaN;
646 QPointF end( dimX.
end, dimY.
end );
651 end.setY( dimY.
start );
654 start.setY( dimY.
end );
657 end.setX( dimX.
start );
660 start.setX( dimX.
end );
664 transversePosition = geoXy( start.y(), start.x() );
666 QPointF transStart = plane->translate( start );
667 QPointF transEnd = plane->translate( end );
675 transverseScreenSpaceShift = geo.top() - transStart.y();
678 transverseScreenSpaceShift = geo.bottom() - transStart.y();
681 transverseScreenSpaceShift = geo.right() - transStart.x();
684 transverseScreenSpaceShift = geo.left() - transStart.x();
688 geoXy.lvalue( transStart.ry(), transStart.rx() ) += transverseScreenSpaceShift;
689 geoXy.lvalue( transEnd.ry(), transEnd.rx() ) += transverseScreenSpaceShift;
692 bool clipSaved = context->
painter()->hasClipping();
693 painter->setClipping(
false );
694 painter->drawLine( transStart, transEnd );
695 painter->setClipping( clipSaved );
704 int labelThinningFactor = 1;
710 QPointF prevTickLabelPos;
716 for (
int step = labelTA.
isVisible() ? Layout : Painting; step < Done; step++ ) {
718 bool isFirstLabel =
true;
719 for (
TickIterator it(
this, plane, labelThinningFactor, centerTicks ); !it.isAtEnd(); ++it ) {
720 if ( skipFirstTick ) {
721 skipFirstTick =
false;
725 const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
726 QPointF onAxis = plane->translate( geoXy( QPointF( drawPos, transversePosition ) ,
727 QPointF( transversePosition, drawPos ) ) );
728 geoXy.lvalue( onAxis.ry(), onAxis.rx() ) += transverseScreenSpaceShift;
733 QPointF tickEnd = onAxis;
734 qreal tickLen = it.type() == TickIterator::CustomTick ?
735 d->customTickLength :
tickLength( it.type() == TickIterator::MinorTick );
736 geoXy.lvalue( tickEnd.ry(), tickEnd.rx() ) += isOutwardsPositive ? tickLen : -tickLen;
746 if ( step == Painting ) {
749 painter->setPen( rulerAttr.
tickMarkPen( it.position() ) );
751 painter->setPen( it.type() == TickIterator::MinorTick ? rulerAttr.
minorTickMarkPen()
754 painter->drawLine( onAxis, tickEnd );
758 if ( it.text().isEmpty() || !labelTA.
isVisible() ) {
765 QString text = it.text();
766 if ( it.type() == TickIterator::MajorTick ) {
767 text =
d->customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
770 QSizeF size = QSizeF( tickLabel->
sizeHint() );
772 Q_ASSERT( labelPoly.count() == 4 );
779 axisAngle = 0;
break;
781 axisAngle = 180;
break;
783 axisAngle = 270;
break;
785 axisAngle = 90;
break;
792 int relAngle = axisAngle - labelTA.
rotation() + 45;
793 if ( relAngle < 0 ) {
796 int polyCorner1 = relAngle / 90;
797 QPoint p1 = labelPoly.at( polyCorner1 );
798 QPoint p2 = labelPoly.at( polyCorner1 == 3 ? 0 : ( polyCorner1 + 1 ) );
800 QPointF labelPos = tickEnd;
803 if ( labelMargin < 0 ) {
804 labelMargin = QFontMetricsF( tickLabel->
realFont() ).height() * 0.5;
810 labelPos += QPointF( -size.width() - labelMargin,
811 -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
814 labelPos += QPointF( labelMargin,
815 -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
818 labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
819 -size.height() - labelMargin );
822 labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
827 tickLabel->
setGeometry( QRect( labelPos.toPoint(), size.toSize() ) );
829 if ( step == Painting ) {
830 tickLabel->
paint( painter );
838 if ( step == Layout ) {
839 int spaceSavingRotation = geoXy( 270, 0 );
841 const bool canShortenLabels = !geoXy.isY && it.type() == TickIterator::MajorTickManualLong &&
842 it.hasShorterLabels();
843 bool collides =
false;
844 if ( it.type() == TickIterator::MajorTick || it.type() == TickIterator::MajorTickHeaderDataLabel
845 || canShortenLabels || canRotate ) {
846 if ( isFirstLabel ) {
847 isFirstLabel =
false;
849 collides = tickLabel->
intersects( *prevTickLabel, labelPos, prevTickLabelPos );
850 qSwap( prevTickLabel, tickLabel );
852 prevTickLabelPos = labelPos;
856 if ( canRotate && !canShortenLabels ) {
861 labelThinningFactor++;
871 delete prevTickLabel;
875 d->drawTitleText( painter, plane,
d->axis()->geometry() );
888 Qt::Orientations ret;
892 ret = Qt::Horizontal;
907 d->cachedMaximumSize = QSize();
913 if ( !
d->cachedMaximumSize.isValid() )
914 d->cachedMaximumSize =
d->calculateMaximumSize();
915 return d->cachedMaximumSize;
918 QSize CartesianAxis::Private::calculateMaximumSize()
const
928 && axis()->isAbscissa();
936 XySwitch geoXy( isVertical() );
941 qreal startOverhang = 0.0;
942 qreal endOverhang = 0.0;
944 if ( mAxis->textAttributes().isVisible() ) {
946 qreal lowestLabelPosition = signalingNaN;
947 qreal highestLabelPosition = signalingNaN;
948 qreal lowestLabelLongitudinalSize = signalingNaN;
949 qreal highestLabelLongitudinalSize = signalingNaN;
951 TextLayoutItem tickLabel( QString(), mAxis->textAttributes(), refArea,
956 for ( TickIterator it( axis(), plane, 1, centerTicks ); !it.isAtEnd(); ++it ) {
957 const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
958 if ( !showFirstTick ) {
959 showFirstTick =
true;
963 qreal labelSizeTransverse = 0.0;
964 qreal labelMargin = 0.0;
965 if ( !it.text().isEmpty() ) {
966 QPointF labelPosition = plane->
translate( QPointF( geoXy( drawPos, 1.0 ),
967 geoXy( 1.0, drawPos ) ) );
968 highestLabelPosition = geoXy( labelPosition.x(), labelPosition.y() );
970 QString text = it.text();
971 if ( it.type() == TickIterator::MajorTick ) {
972 text = customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
974 tickLabel.setText( text );
976 QSize sz = tickLabel.sizeHint();
977 highestLabelLongitudinalSize = geoXy( sz.width(), sz.height() );
978 if ( ISNAN( lowestLabelLongitudinalSize ) ) {
979 lowestLabelLongitudinalSize = highestLabelLongitudinalSize;
980 lowestLabelPosition = highestLabelPosition;
983 labelSizeTransverse = geoXy( sz.height(), sz.width() );
984 labelMargin = rulerAttr.labelMargin();
985 if ( labelMargin < 0 ) {
986 labelMargin = QFontMetricsF( tickLabel.realFont() ).height() * 0.5;
988 labelMargin -= tickLabel.marginWidth();
990 qreal tickLength = it.type() == TickIterator::CustomTick ?
991 customTickLength : axis()->tickLength( it.type() == TickIterator::MinorTick );
992 size = qMax( size, tickLength + labelMargin + labelSizeTransverse );
999 const qreal lowestPosition = geoXy( pt.x(), pt.y() );
1001 const qreal highestPosition = geoXy( pt.x(), pt.y() );
1004 startOverhang = qMax( 0.0, ( lowestPosition - lowestLabelPosition ) * geoXy( 1.0, -1.0 ) +
1005 lowestLabelLongitudinalSize * 0.5 );
1006 endOverhang = qMax( 0.0, ( highestLabelPosition - highestPosition ) * geoXy( 1.0, -1.0 ) +
1007 highestLabelLongitudinalSize * 0.5 );
1010 amountOfLeftOverlap = geoXy( startOverhang, 0.0 );
1011 amountOfRightOverlap = geoXy( endOverhang, 0.0 );
1012 amountOfBottomOverlap = geoXy( 0.0, startOverhang );
1013 amountOfTopOverlap = geoXy( 0.0, endOverhang );
1015 const TextAttributes titleTA = titleTextAttributesWithAdjustedRotation();
1016 if ( titleTA.
isVisible() && !axis()->titleText().isEmpty() ) {
1017 TextLayoutItem title( axis()->titleText(), titleTA, refArea, KDChartEnums::MeasureOrientationMinimum,
1018 Qt::AlignHCenter | Qt::AlignVCenter );
1021 size += geoXy( titleFM.height() * 0.33, titleFM.averageCharWidth() * 0.55 );
1022 size += geoXy( title.sizeHint().height(), title.sizeHint().width() );
1026 return QSize( geoXy( 1,
int( size ) ), geoXy(
int ( size ), 1 ) );
1044 if (
d->geometry != r ) {
1058 d->customTickLength = value;
1063 return d->customTickLength;
1074 return d->annotations;
1079 if (
d->annotations == annotations )
1088 return d->customTicksPositions;
1093 if (
d->customTicksPositions == customTicksPositions )
1096 d->customTicksPositions = customTicksPositions;