1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "VCartesianAxis.hxx"
21 #include <PlottingPositionHelper.hxx>
22 #include <ShapeFactory.hxx>
23 #include <PropertyMapper.hxx>
24 #include <NumberFormatterWrapper.hxx>
25 #include <LabelPositionHelper.hxx>
26 #include <BaseGFXHelper.hxx>
28 #include <AxisHelper.hxx>
29 #include "Tickmarks_Equidistant.hxx"
30 #include <ExplicitCategoriesProvider.hxx>
31 #include <com/sun/star/chart2/AxisType.hpp>
32 #include <o3tl/safeint.hxx>
33 #include <rtl/math.hxx>
34 #include <comphelper/diagnose_ex.hxx>
35 #include <tools/color.hxx>
36 #include <svx/unoshape.hxx>
37 #include <svx/unoshtxt.hxx>
38 #include <VSeriesPlotter.hxx>
39 #include <DataTableView.hxx>
40 #include <ChartModel.hxx>
42 #include <comphelper/scopeguard.hxx>
44 #include <basegfx/polygon/b2dpolygon.hxx>
45 #include <basegfx/polygon/b2dpolypolygon.hxx>
46 #include <basegfx/polygon/b2dpolygontools.hxx>
47 #include <basegfx/polygon/b2dpolygonclipper.hxx>
48 #include <basegfx/matrix/b2dhommatrix.hxx>
49 #include <basegfx/numeric/ftools.hxx>
55 using namespace ::com::sun::star
;
56 using ::com::sun::star::uno::Reference
;
57 using ::basegfx::B2DVector
;
58 using ::basegfx::B2DPolygon
;
59 using ::basegfx::B2DPolyPolygon
;
63 VCartesianAxis::VCartesianAxis( const AxisProperties
& rAxisProperties
64 , const Reference
< util::XNumberFormatsSupplier
>& xNumberFormatsSupplier
65 , sal_Int32 nDimensionIndex
, sal_Int32 nDimensionCount
66 , PlottingPositionHelper
* pPosHelper
)//takes ownership
67 : VAxisBase( nDimensionIndex
, nDimensionCount
, rAxisProperties
, xNumberFormatsSupplier
)
70 m_pPosHelper
= pPosHelper
;
72 m_pPosHelper
= new PlottingPositionHelper();
75 VCartesianAxis::~VCartesianAxis()
78 m_pPosHelper
= nullptr;
81 static void lcl_ResizeTextShapeToFitAvailableSpace( SvxShapeText
& rShape2DText
,
82 const AxisLabelProperties
& rAxisLabelProperties
,
83 std::u16string_view rLabel
,
84 const tNameSequence
& rPropNames
,
85 const tAnySequence
& rPropValues
,
86 const bool bIsHorizontalAxis
)
88 bool bTextHorizontal
= rAxisLabelProperties
.m_fRotationAngleDegree
!= 0.0;
89 bool bIsDirectionVertical
= bIsHorizontalAxis
&& bTextHorizontal
;
90 const sal_Int32 nFullSize
= bIsDirectionVertical
? rAxisLabelProperties
.m_aFontReferenceSize
.Height
: rAxisLabelProperties
.m_aFontReferenceSize
.Width
;
92 if( !nFullSize
|| rLabel
.empty() )
95 const sal_Int32 nAvgCharWidth
= rShape2DText
.getSize().Width
/ rLabel
.size();
97 sal_Int32 nMaxLabelsSize
= bIsDirectionVertical
? rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Height
: rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Width
;
99 awt::Size aSizeAfterRotation
= ShapeFactory::getSizeAfterRotation(rShape2DText
, rAxisLabelProperties
.m_fRotationAngleDegree
);
101 const sal_Int32 nTextSize
= bIsDirectionVertical
? aSizeAfterRotation
.Height
: aSizeAfterRotation
.Width
;
106 static constexpr OUString sDots
= u
"..."_ustr
;
107 const sal_Int32 nCharsToRemove
= ( nTextSize
- nMaxLabelsSize
) / nAvgCharWidth
+ 1;
108 sal_Int32 nNewLen
= rLabel
.size() - nCharsToRemove
- sDots
.getLength();
109 // Prevent from showing only dots
111 nNewLen
= ( sal_Int32(rLabel
.size()) >= sDots
.getLength() ) ? sDots
.getLength() : rLabel
.size();
113 bool bCrop
= nCharsToRemove
> 0;
117 OUString
aNewLabel( rLabel
.substr( 0, nNewLen
) );
118 if( nNewLen
> sDots
.getLength() )
120 rShape2DText
.setString( aNewLabel
);
122 PropertyMapper::setMultiProperties( rPropNames
, rPropValues
, rShape2DText
);
125 static rtl::Reference
<SvxShapeText
> createSingleLabel(
126 const rtl::Reference
< SvxShapeGroupAnyD
>& xTarget
127 , const awt::Point
& rAnchorScreenPosition2D
128 , const OUString
& rLabel
129 , const AxisLabelProperties
& rAxisLabelProperties
130 , const AxisProperties
& rAxisProperties
131 , const tNameSequence
& rPropNames
132 , const tAnySequence
& rPropValues
133 , const bool bIsHorizontalAxis
139 // #i78696# use mathematically correct rotation now
140 const double fRotationAnglePi(-basegfx::deg2rad(rAxisLabelProperties
.m_fRotationAngleDegree
));
141 uno::Any aATransformation
= ShapeFactory::makeTransformation( rAnchorScreenPosition2D
, fRotationAnglePi
);
142 OUString aLabel
= ShapeFactory::getStackedString( rLabel
, rAxisLabelProperties
.m_bStackCharacters
);
144 rtl::Reference
<SvxShapeText
> xShape2DText
=
145 ShapeFactory::createText( xTarget
, aLabel
, rPropNames
, rPropValues
, aATransformation
);
147 if( rAxisProperties
.m_bLimitSpaceForLabels
)
148 lcl_ResizeTextShapeToFitAvailableSpace(*xShape2DText
, rAxisLabelProperties
, aLabel
, rPropNames
, rPropValues
, bIsHorizontalAxis
);
150 LabelPositionHelper::correctPositionForRotation( xShape2DText
151 , rAxisProperties
.maLabelAlignment
.meAlignment
, rAxisLabelProperties
.m_fRotationAngleDegree
, rAxisProperties
.m_bComplexCategories
);
156 static bool lcl_doesShapeOverlapWithTickmark( SvxShape
& rShape
157 , double fRotationAngleDegree
158 , const basegfx::B2DVector
& rTickScreenPosition
)
160 ::basegfx::B2IRectangle aShapeRect
= BaseGFXHelper::makeRectangle(rShape
.getPosition(), ShapeFactory::getSizeAfterRotation( rShape
, fRotationAngleDegree
));
162 basegfx::B2IVector
aPosition(
163 static_cast<sal_Int32
>( rTickScreenPosition
.getX() )
164 , static_cast<sal_Int32
>( rTickScreenPosition
.getY() ) );
165 return aShapeRect
.isInside(aPosition
);
168 static void lcl_getRotatedPolygon( B2DPolygon
&aPoly
, const ::basegfx::B2DRectangle
&aRect
, const awt::Point
&aPos
, const double fRotationAngleDegree
)
170 aPoly
= basegfx::utils::createPolygonFromRect( aRect
);
172 // For rotating the rectangle we use the opposite angle,
173 // since `B2DHomMatrix` class used for
174 // representing the transformation, performs rotations in the positive
175 // direction (from the X axis to the Y axis). However since the coordinate
176 // system used by the chart has the Y-axis pointing downward, a rotation in
177 // the positive direction means a clockwise rotation. On the contrary text
178 // labels are rotated counterclockwise.
179 // The rotation is performed around the top-left vertex of the rectangle
180 // which is then moved to its final position by using the top-left
181 // vertex of the text label bounding box (aPos) as the translation vector.
182 ::basegfx::B2DHomMatrix aMatrix
;
183 aMatrix
.rotate(-basegfx::deg2rad(fRotationAngleDegree
));
184 aMatrix
.translate( aPos
.X
, aPos
.Y
);
185 aPoly
.transform( aMatrix
);
188 static bool doesOverlap( const rtl::Reference
<SvxShapeText
>& xShape1
189 , const rtl::Reference
<SvxShapeText
>& xShape2
190 , double fRotationAngleDegree
)
192 if( !xShape1
.is() || !xShape2
.is() )
195 ::basegfx::B2DRectangle
aRect1( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape1
->getSize()));
196 ::basegfx::B2DRectangle
aRect2( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape2
->getSize()));
200 lcl_getRotatedPolygon( aPoly1
, aRect1
, xShape1
->getPosition(), fRotationAngleDegree
);
201 lcl_getRotatedPolygon( aPoly2
, aRect2
, xShape2
->getPosition(), fRotationAngleDegree
);
203 B2DPolyPolygon aPolyPoly1
, aPolyPoly2
;
204 aPolyPoly1
.append( aPoly1
);
205 aPolyPoly2
.append( aPoly2
);
206 B2DPolyPolygon overlapPoly
= ::basegfx::utils::clipPolyPolygonOnPolyPolygon( aPolyPoly1
, aPolyPoly2
, true, false );
208 return (overlapPoly
.count() > 0);
211 static void removeShapesAtWrongRhythm( TickIter
& rIter
212 , sal_Int32 nCorrectRhythm
213 , sal_Int32 nMaxTickToCheck
214 , const rtl::Reference
< SvxShapeGroupAnyD
>& xTarget
)
217 for( TickInfo
* pTickInfo
= rIter
.firstInfo()
218 ; pTickInfo
&& nTick
<= nMaxTickToCheck
219 ; pTickInfo
= rIter
.nextInfo(), nTick
++ )
221 //remove labels which does not fit into the rhythm
222 if( nTick
%nCorrectRhythm
!= 0)
224 if(pTickInfo
->xTextShape
.is())
226 xTarget
->remove(pTickInfo
->xTextShape
);
227 pTickInfo
->xTextShape
= nullptr;
236 * If the labels are staggered and bInnerLine is true we iterate through
237 * only those labels that are closer to the diagram.
239 * If the labels are staggered and bInnerLine is false we iterate through
240 * only those that are farther from the diagram.
242 * If the labels are not staggered we iterate through all labels.
244 class LabelIterator
: public TickIter
247 LabelIterator( TickInfoArrayType
& rTickInfoVector
248 , const AxisLabelStaggering eAxisLabelStaggering
251 virtual TickInfo
* firstInfo() override
;
252 virtual TickInfo
* nextInfo() override
;
255 PureTickIter m_aPureTickIter
;
256 const AxisLabelStaggering m_eAxisLabelStaggering
;
262 LabelIterator::LabelIterator( TickInfoArrayType
& rTickInfoVector
263 , const AxisLabelStaggering eAxisLabelStaggering
265 : m_aPureTickIter( rTickInfoVector
)
266 , m_eAxisLabelStaggering(eAxisLabelStaggering
)
267 , m_bInnerLine(bInnerLine
)
271 TickInfo
* LabelIterator::firstInfo()
273 TickInfo
* pTickInfo
= m_aPureTickIter
.firstInfo();
274 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() )
275 pTickInfo
= m_aPureTickIter
.nextInfo();
278 if( (m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerEven
&& m_bInnerLine
)
280 (m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerOdd
&& !m_bInnerLine
)
285 pTickInfo
= m_aPureTickIter
.nextInfo();
286 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() );
293 TickInfo
* LabelIterator::nextInfo()
295 TickInfo
* pTickInfo
= nullptr;
298 pTickInfo
= m_aPureTickIter
.nextInfo();
299 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() );
301 if( m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerEven
302 || m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerOdd
)
306 pTickInfo
= m_aPureTickIter
.nextInfo();
307 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() );
312 static B2DVector
lcl_getLabelsDistance( TickIter
& rIter
, const B2DVector
& rDistanceTickToText
, double fRotationAngleDegree
)
314 //calculates the height or width of a line of labels
315 //thus a following line of labels can be shifted for that distance
319 sal_Int32 nDistanceTickToText
= static_cast<sal_Int32
>( rDistanceTickToText
.getLength() );
320 if( nDistanceTickToText
==0.0)
323 B2DVector
aStaggerDirection(rDistanceTickToText
);
324 aStaggerDirection
.normalize();
326 sal_Int32 nDistance
=0;
327 rtl::Reference
< SvxShapeText
> xShape2DText
;
328 for( TickInfo
* pTickInfo
= rIter
.firstInfo()
330 ; pTickInfo
= rIter
.nextInfo() )
332 xShape2DText
= pTickInfo
->xTextShape
;
333 if( xShape2DText
.is() )
335 awt::Size aSize
= ShapeFactory::getSizeAfterRotation( *xShape2DText
, fRotationAngleDegree
);
336 if(fabs(aStaggerDirection
.getX())>fabs(aStaggerDirection
.getY()))
337 nDistance
= std::max(nDistance
,aSize
.Width
);
339 nDistance
= std::max(nDistance
,aSize
.Height
);
343 aRet
= aStaggerDirection
*nDistance
;
345 //add extra distance for vertical distance
346 if(fabs(aStaggerDirection
.getX())>fabs(aStaggerDirection
.getY()))
347 aRet
+= rDistanceTickToText
;
352 static void lcl_shiftLabels( TickIter
& rIter
, const B2DVector
& rStaggerDistance
)
354 if(rStaggerDistance
.getLength()==0.0)
356 for( TickInfo
* pTickInfo
= rIter
.firstInfo()
358 ; pTickInfo
= rIter
.nextInfo() )
360 const rtl::Reference
<SvxShapeText
>& xShape2DText
= pTickInfo
->xTextShape
;
361 if( xShape2DText
.is() )
363 awt::Point aPos
= xShape2DText
->getPosition();
364 aPos
.X
+= static_cast<sal_Int32
>(rStaggerDistance
.getX());
365 aPos
.Y
+= static_cast<sal_Int32
>(rStaggerDistance
.getY());
366 xShape2DText
->setPosition( aPos
);
371 static bool lcl_hasWordBreak( const rtl::Reference
<SvxShapeText
>& xShape
)
376 SvxTextEditSource
* pTextEditSource
= dynamic_cast<SvxTextEditSource
*>(xShape
->GetEditSource());
377 if (!pTextEditSource
)
380 pTextEditSource
->UpdateOutliner();
381 SvxTextForwarder
* pTextForwarder
= pTextEditSource
->GetTextForwarder();
385 sal_Int32 nParaCount
= pTextForwarder
->GetParagraphCount();
386 for ( sal_Int32 nPara
= 0; nPara
< nParaCount
; ++nPara
)
388 sal_Int32 nLineCount
= pTextForwarder
->GetLineCount( nPara
);
389 for ( sal_Int32 nLine
= 0; nLine
< nLineCount
; ++nLine
)
391 sal_Int32 nLineStart
= 0;
392 sal_Int32 nLineEnd
= 0;
393 pTextForwarder
->GetLineBoundaries( nLineStart
, nLineEnd
, nPara
, nLine
);
394 assert(nLineStart
>= 0);
395 sal_Int32 nWordStart
= 0;
396 sal_Int32 nWordEnd
= 0;
397 if ( pTextForwarder
->GetWordIndices( nPara
, nLineStart
, nWordStart
, nWordEnd
) &&
398 ( nWordStart
!= nLineStart
) )
408 static OUString
getTextLabelString(
409 const FixedNumberFormatter
& rFixedNumberFormatter
, const uno::Sequence
<OUString
>* pCategories
,
410 const TickInfo
* pTickInfo
, bool bComplexCat
, Color
& rExtraColor
, bool& rHasExtraColor
)
414 // This is a normal category axis. Get the label string from the
415 // label string array.
416 sal_Int32 nIndex
= static_cast<sal_Int32
>(pTickInfo
->getUnscaledTickValue()) - 1; //first category (index 0) matches with real number 1.0
417 if( nIndex
>=0 && nIndex
<pCategories
->getLength() )
418 return (*pCategories
)[nIndex
];
422 else if (bComplexCat
)
424 // This is a complex category axis. The label is stored in the tick.
425 return pTickInfo
->aText
;
428 // This is a numeric axis. Format the original tick value per number format.
429 return rFixedNumberFormatter
.getFormattedString(pTickInfo
->getUnscaledTickValue(), rExtraColor
, rHasExtraColor
);
432 static void getAxisLabelProperties(
433 tNameSequence
& rPropNames
, tAnySequence
& rPropValues
, const AxisProperties
& rAxisProp
,
434 const AxisLabelProperties
& rAxisLabelProp
,
435 sal_Int32 nLimitedSpaceForText
, bool bLimitedHeight
)
437 Reference
<beans::XPropertySet
> xProps(rAxisProp
.m_xAxisModel
);
439 PropertyMapper::getTextLabelMultiPropertyLists(
440 xProps
, rPropNames
, rPropValues
, false, nLimitedSpaceForText
, bLimitedHeight
, false);
442 LabelPositionHelper::doDynamicFontResize(
443 rPropValues
, rPropNames
, xProps
, rAxisLabelProp
.m_aFontReferenceSize
);
445 LabelPositionHelper::changeTextAdjustment(
446 rPropValues
, rPropNames
, rAxisProp
.maLabelAlignment
.meAlignment
);
452 * Iterate through only 3 ticks including the one that has the longest text
453 * length. When the first tick has the longest text, it iterates through
454 * the first 3 ticks. Otherwise it iterates through 3 ticks such that the
455 * 2nd tick is the one with the longest text.
457 class MaxLabelTickIter
: public TickIter
460 MaxLabelTickIter( TickInfoArrayType
& rTickInfoVector
, size_t nLongestLabelIndex
);
462 virtual TickInfo
* firstInfo() override
;
463 virtual TickInfo
* nextInfo() override
;
466 TickInfoArrayType
& m_rTickInfoVector
;
467 std::vector
<size_t> m_aValidIndices
;
468 size_t m_nCurrentIndex
;
473 MaxLabelTickIter::MaxLabelTickIter(
474 TickInfoArrayType
& rTickInfoVector
, size_t nLongestLabelIndex
) :
475 m_rTickInfoVector(rTickInfoVector
), m_nCurrentIndex(0)
477 assert(!rTickInfoVector
.empty()); // should be checked by the caller.
478 assert(nLongestLabelIndex
< rTickInfoVector
.size());
480 size_t nMaxIndex
= m_rTickInfoVector
.size()-1;
481 if (nLongestLabelIndex
>= nMaxIndex
-1)
482 nLongestLabelIndex
= 0;
484 if (nLongestLabelIndex
> 0)
485 m_aValidIndices
.push_back(nLongestLabelIndex
-1);
487 m_aValidIndices
.push_back(nLongestLabelIndex
);
489 while (m_aValidIndices
.size() < 3)
491 ++nLongestLabelIndex
;
492 if (nLongestLabelIndex
> nMaxIndex
)
495 m_aValidIndices
.push_back(nLongestLabelIndex
);
499 TickInfo
* MaxLabelTickIter::firstInfo()
502 if (m_nCurrentIndex
< m_aValidIndices
.size())
503 return &m_rTickInfoVector
[m_aValidIndices
[m_nCurrentIndex
]];
507 TickInfo
* MaxLabelTickIter::nextInfo()
510 if (m_nCurrentIndex
< m_aValidIndices
.size())
511 return &m_rTickInfoVector
[m_aValidIndices
[m_nCurrentIndex
]];
515 bool VCartesianAxis::isBreakOfLabelsAllowed(
516 const AxisLabelProperties
& rAxisLabelProperties
, bool bIsHorizontalAxis
, bool bIsVerticalAxis
) const
518 if( m_aTextLabels
.getLength() > 100 )
520 if( !rAxisLabelProperties
.m_bLineBreakAllowed
)
522 if( rAxisLabelProperties
.m_bStackCharacters
)
524 //no break for value axis
525 if( !m_bUseTextLabels
)
527 if( !( rAxisLabelProperties
.m_fRotationAngleDegree
== 0.0 ||
528 rAxisLabelProperties
.m_fRotationAngleDegree
== 90.0 ||
529 rAxisLabelProperties
.m_fRotationAngleDegree
== 270.0 ) )
531 //no break for complex vertical category axis
532 if( !m_aAxisProperties
.m_bSwapXAndY
)
533 return bIsHorizontalAxis
;
534 else if( m_aAxisProperties
.m_bSwapXAndY
&& !m_aAxisProperties
.m_bComplexCategories
)
535 return bIsVerticalAxis
;
541 bool canAutoAdjustLabelPlacement(
542 const AxisLabelProperties
& rAxisLabelProperties
, bool bIsHorizontalAxis
, bool bIsVerticalAxis
)
544 // joined prerequisite checks for auto rotate and auto stagger
545 if( rAxisLabelProperties
.m_bOverlapAllowed
)
547 if( rAxisLabelProperties
.m_bLineBreakAllowed
) // auto line break may conflict with...
549 if( rAxisLabelProperties
.m_fRotationAngleDegree
!= 0.0 )
551 // automatic adjusting labels only works for
552 // horizontal axis with horizontal text
553 // or vertical axis with vertical text
554 if( bIsHorizontalAxis
)
555 return !rAxisLabelProperties
.m_bStackCharacters
;
556 if( bIsVerticalAxis
)
557 return rAxisLabelProperties
.m_bStackCharacters
;
561 bool isAutoStaggeringOfLabelsAllowed(
562 const AxisLabelProperties
& rAxisLabelProperties
, bool bIsHorizontalAxis
, bool bIsVerticalAxis
)
564 if( rAxisLabelProperties
.m_eStaggering
!= AxisLabelStaggering::StaggerAuto
)
566 return canAutoAdjustLabelPlacement(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
);
569 // make clear that we check for auto rotation prerequisites
570 const auto& isAutoRotatingOfLabelsAllowed
= canAutoAdjustLabelPlacement
;
573 void VCartesianAxis::createAllTickInfosFromComplexCategories( TickInfoArraysType
& rAllTickInfos
, bool bShiftedPosition
)
575 //no minor tickmarks will be generated!
576 //order is: inner labels first , outer labels last (that is different to all other TickIter cases)
577 if(!bShiftedPosition
)
579 rAllTickInfos
.clear();
581 sal_Int32 nLevelCount
= m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoryLevelCount();
582 for( ; nLevel
<nLevelCount
; nLevel
++ )
584 TickInfoArrayType aTickInfoVector
;
585 const std::vector
<ComplexCategory
>* pComplexCategories
=
586 m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoriesByLevel(nLevel
);
588 if (!pComplexCategories
)
591 sal_Int32 nCatIndex
= 0;
593 for (auto const& complexCategory
: *pComplexCategories
)
595 TickInfo
aTickInfo(nullptr);
596 sal_Int32 nCount
= complexCategory
.Count
;
597 if( nCatIndex
+ 1.0 + nCount
>= m_aScale
.Maximum
)
599 nCount
= static_cast<sal_Int32
>(m_aScale
.Maximum
- 1.0 - nCatIndex
);
603 aTickInfo
.fScaledTickValue
= nCatIndex
+ 1.0 + nCount
/2.0;
604 aTickInfo
.nFactorForLimitedTextWidth
= nCount
;
605 aTickInfo
.aText
= complexCategory
.Text
;
606 aTickInfoVector
.push_back(aTickInfo
);
608 if( nCatIndex
+ 1.0 >= m_aScale
.Maximum
)
611 rAllTickInfos
.push_back(aTickInfoVector
);
614 else //bShiftedPosition==false
616 rAllTickInfos
.clear();
618 sal_Int32 nLevelCount
= m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoryLevelCount();
619 for( ; nLevel
<nLevelCount
; nLevel
++ )
621 TickInfoArrayType aTickInfoVector
;
622 const std::vector
<ComplexCategory
>* pComplexCategories
=
623 m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoriesByLevel(nLevel
);
624 sal_Int32 nCatIndex
= 0;
625 if (pComplexCategories
)
627 for (auto const& complexCategory
: *pComplexCategories
)
629 TickInfo
aTickInfo(nullptr);
630 aTickInfo
.fScaledTickValue
= nCatIndex
+ 1.0;
631 aTickInfoVector
.push_back(aTickInfo
);
632 nCatIndex
+= complexCategory
.Count
;
633 if( nCatIndex
+ 1.0 > m_aScale
.Maximum
)
638 //fill up with single ticks until maximum scale
639 while( nCatIndex
+ 1.0 < m_aScale
.Maximum
)
641 TickInfo
aTickInfo(nullptr);
642 aTickInfo
.fScaledTickValue
= nCatIndex
+ 1.0;
643 aTickInfoVector
.push_back(aTickInfo
);
648 //add an additional tick at the end
650 TickInfo
aTickInfo(nullptr);
651 aTickInfo
.fScaledTickValue
= m_aScale
.Maximum
;
652 aTickInfoVector
.push_back(aTickInfo
);
654 rAllTickInfos
.push_back(aTickInfoVector
);
659 void VCartesianAxis::createAllTickInfos( TickInfoArraysType
& rAllTickInfos
)
661 if( isComplexCategoryAxis() )
662 createAllTickInfosFromComplexCategories( rAllTickInfos
, false );
664 VAxisBase::createAllTickInfos(rAllTickInfos
);
667 TickIter
* VCartesianAxis::createLabelTickIterator( sal_Int32 nTextLevel
)
669 if( nTextLevel
>=0 && o3tl::make_unsigned(nTextLevel
) < m_aAllTickInfos
.size() )
670 return new PureTickIter( m_aAllTickInfos
[nTextLevel
] );
674 TickIter
* VCartesianAxis::createMaximumLabelTickIterator( sal_Int32 nTextLevel
)
676 if( isComplexCategoryAxis() || isDateAxis() )
678 return createLabelTickIterator( nTextLevel
); //mmmm maybe todo: create less than all texts here
684 if( !m_aAllTickInfos
.empty() )
686 size_t nLongestLabelIndex
= m_bUseTextLabels
? getIndexOfLongestLabel(m_aTextLabels
) : 0;
687 if (nLongestLabelIndex
>= m_aAllTickInfos
[0].size())
690 return new MaxLabelTickIter( m_aAllTickInfos
[0], nLongestLabelIndex
);
697 sal_Int32
VCartesianAxis::getTextLevelCount() const
699 sal_Int32 nTextLevelCount
= 1;
700 if( isComplexCategoryAxis() )
701 nTextLevelCount
= m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoryLevelCount();
702 return nTextLevelCount
;
705 bool VCartesianAxis::createTextShapes(
706 const rtl::Reference
< SvxShapeGroupAnyD
>& xTarget
, TickIter
& rTickIter
,
707 AxisLabelProperties
& rAxisLabelProperties
, TickFactory2D
const * pTickFactory
,
708 sal_Int32 nScreenDistanceBetweenTicks
)
710 const bool bIsHorizontalAxis
= pTickFactory
->isHorizontalAxis();
711 const bool bIsVerticalAxis
= pTickFactory
->isVerticalAxis();
713 if( m_bUseTextLabels
&& (m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_NEAR_AXIS
||
714 m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_START
))
716 if (bIsHorizontalAxis
)
718 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Y
= pTickFactory
->getXaxisStartPos().getY();
719 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Height
= rAxisLabelProperties
.m_aFontReferenceSize
.Height
- rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Y
;
721 else if (bIsVerticalAxis
)
723 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.X
= 0;
724 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Width
= pTickFactory
->getXaxisStartPos().getX();
728 bool bIsBreakOfLabelsAllowed
= isBreakOfLabelsAllowed( rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
);
729 if (!bIsBreakOfLabelsAllowed
&&
730 !isAutoStaggeringOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) &&
731 !rAxisLabelProperties
.isStaggered())
733 return createTextShapesSimple(xTarget
, rTickIter
, rAxisLabelProperties
, pTickFactory
);
736 FixedNumberFormatter
aFixedNumberFormatter(
737 m_xNumberFormatsSupplier
, rAxisLabelProperties
.m_nNumberFormatKey
);
739 bool bIsStaggered
= rAxisLabelProperties
.isStaggered();
740 B2DVector aTextToTickDistance
= pTickFactory
->getDistanceAxisTickToText(m_aAxisProperties
, true);
741 sal_Int32 nLimitedSpaceForText
= -1;
743 if (bIsBreakOfLabelsAllowed
)
745 if (!m_aAxisProperties
.m_bLimitSpaceForLabels
)
747 basegfx::B2DVector nDeltaVector
= pTickFactory
->getXaxisEndPos() - pTickFactory
->getXaxisStartPos();
748 nLimitedSpaceForText
= nDeltaVector
.getX();
750 if (nScreenDistanceBetweenTicks
> 0)
751 nLimitedSpaceForText
= nScreenDistanceBetweenTicks
;
754 nLimitedSpaceForText
*= 2;
756 if( nLimitedSpaceForText
> 0 )
757 { //reduce space for a small amount to have a visible distance between the labels:
758 sal_Int32 nReduce
= (nLimitedSpaceForText
*5)/100;
761 nLimitedSpaceForText
-= nReduce
;
764 // recalculate the nLimitedSpaceForText in case of 90 and 270 degree if the text break is true
765 if ( rAxisLabelProperties
.m_fRotationAngleDegree
== 90.0 || rAxisLabelProperties
.m_fRotationAngleDegree
== 270.0 )
767 nLimitedSpaceForText
= rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Height
;
768 m_aAxisProperties
.m_bLimitSpaceForLabels
= false;
771 // recalculate the nLimitedSpaceForText in case of vertical category axis if the text break is true
772 if ( m_aAxisProperties
.m_bSwapXAndY
&& bIsVerticalAxis
&& rAxisLabelProperties
.m_fRotationAngleDegree
== 0.0 )
774 nLimitedSpaceForText
= pTickFactory
->getXaxisStartPos().getX();
775 m_aAxisProperties
.m_bLimitSpaceForLabels
= false;
779 // Stores an array of text label strings in case of a normal
780 // (non-complex) category axis.
781 const uno::Sequence
<OUString
>* pCategories
= nullptr;
782 if( m_bUseTextLabels
&& !m_aAxisProperties
.m_bComplexCategories
)
783 pCategories
= &m_aTextLabels
;
786 if( !m_aAxisProperties
.m_bSwapXAndY
)
787 bLimitedHeight
= fabs(aTextToTickDistance
.getX()) > fabs(aTextToTickDistance
.getY());
789 bLimitedHeight
= fabs(aTextToTickDistance
.getX()) < fabs(aTextToTickDistance
.getY());
790 //prepare properties for multipropertyset-interface of shape
791 tNameSequence aPropNames
;
792 tAnySequence aPropValues
;
793 getAxisLabelProperties(aPropNames
, aPropValues
, m_aAxisProperties
, rAxisLabelProperties
, nLimitedSpaceForText
, bLimitedHeight
);
795 uno::Any
* pColorAny
= PropertyMapper::getValuePointer(aPropValues
,aPropNames
,u
"CharColor");
796 Color nColor
= COL_AUTO
;
798 *pColorAny
>>= nColor
;
800 uno::Any
* pLimitedSpaceAny
= PropertyMapper::getValuePointerForLimitedSpace(aPropValues
,aPropNames
,bLimitedHeight
);
802 const TickInfo
* pPreviousVisibleTickInfo
= nullptr;
803 const TickInfo
* pPREPreviousVisibleTickInfo
= nullptr;
805 for( TickInfo
* pTickInfo
= rTickIter
.firstInfo()
807 ; pTickInfo
= rTickIter
.nextInfo(), nTick
++ )
809 const TickInfo
* pLastVisibleNeighbourTickInfo
= bIsStaggered
?
810 pPREPreviousVisibleTickInfo
: pPreviousVisibleTickInfo
;
812 //don't create labels which does not fit into the rhythm
813 if( nTick
%rAxisLabelProperties
.m_nRhythm
!= 0 )
816 //don't create labels for invisible ticks
817 if( !pTickInfo
->bPaintIt
)
820 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.m_bOverlapAllowed
)
822 // Overlapping is not allowed. If the label overlaps with its
823 // neighboring label, try increasing the tick interval (or rhythm
824 // as it's called) and start over.
826 if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo
->xTextShape
827 , rAxisLabelProperties
.m_fRotationAngleDegree
828 , pTickInfo
->aTickScreenPosition
) )
830 // This tick overlaps with its neighbor. Try to stagger (if
831 // auto staggering is allowed) to avoid overlapping.
833 bool bOverlapsAfterAutoStagger
= true;
834 if( !bIsStaggered
&& isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
837 rAxisLabelProperties
.m_eStaggering
= AxisLabelStaggering::StaggerEven
;
838 pLastVisibleNeighbourTickInfo
= pPREPreviousVisibleTickInfo
;
839 if( !pLastVisibleNeighbourTickInfo
||
840 !lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo
->xTextShape
841 , rAxisLabelProperties
.m_fRotationAngleDegree
842 , pTickInfo
->aTickScreenPosition
) )
843 bOverlapsAfterAutoStagger
= false;
846 if (bOverlapsAfterAutoStagger
)
848 // Still overlaps with its neighbor even after staggering.
849 // Increment the visible tick intervals (if that's
850 // allowed) and start over.
852 rAxisLabelProperties
.m_nRhythm
++;
853 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.m_nRhythm
, nTick
, xTarget
);
859 bool bHasExtraColor
=false;
862 OUString aLabel
= getTextLabelString(
863 aFixedNumberFormatter
, pCategories
, pTickInfo
, isComplexCategoryAxis(),
864 nExtraColor
, bHasExtraColor
);
867 *pColorAny
<<= bHasExtraColor
?nExtraColor
:nColor
;
869 *pLimitedSpaceAny
<<= sal_Int32(nLimitedSpaceForText
*pTickInfo
->nFactorForLimitedTextWidth
);
871 B2DVector aTickScreenPos2D
= pTickInfo
->aTickScreenPosition
;
872 aTickScreenPos2D
+= aTextToTickDistance
;
873 awt::Point
aAnchorScreenPosition2D(
874 static_cast<sal_Int32
>(aTickScreenPos2D
.getX())
875 ,static_cast<sal_Int32
>(aTickScreenPos2D
.getY()));
877 //create single label
878 if(!pTickInfo
->xTextShape
.is())
880 pTickInfo
->xTextShape
= createSingleLabel( xTarget
881 , aAnchorScreenPosition2D
, aLabel
882 , rAxisLabelProperties
, m_aAxisProperties
883 , aPropNames
, aPropValues
, bIsHorizontalAxis
);
885 if(!pTickInfo
->xTextShape
.is())
888 recordMaximumTextSize( *pTickInfo
->xTextShape
, rAxisLabelProperties
.m_fRotationAngleDegree
);
890 // Label has multiple lines and the words are broken
891 if (nLimitedSpaceForText
> 0
892 && !rAxisLabelProperties
.m_bOverlapAllowed
893 && rAxisLabelProperties
.m_fRotationAngleDegree
== 0.0
895 && lcl_hasWordBreak(pTickInfo
->xTextShape
))
897 // Label has multiple lines and belongs to a complex category
898 // axis. Rotate 90 degrees to try to avoid overlaps.
899 if ( m_aAxisProperties
.m_bComplexCategories
)
901 rAxisLabelProperties
.m_fRotationAngleDegree
= 90;
903 rAxisLabelProperties
.m_bLineBreakAllowed
= false;
904 m_aAxisLabelProperties
.m_fRotationAngleDegree
= rAxisLabelProperties
.m_fRotationAngleDegree
;
905 removeTextShapesFromTicks();
909 //if NO OVERLAP -> remove overlapping shapes
910 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.m_bOverlapAllowed
)
912 // Check if the label still overlaps with its neighbor.
913 if( doesOverlap( pLastVisibleNeighbourTickInfo
->xTextShape
, pTickInfo
->xTextShape
, rAxisLabelProperties
.m_fRotationAngleDegree
) )
915 // It overlaps. Check if staggering helps.
916 bool bOverlapsAfterAutoStagger
= true;
917 if( !bIsStaggered
&& isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
919 // Compatibility option: starting from LibreOffice 5.1 the rotated
920 // layout is preferred to staggering for axis labels.
921 if( !isAutoRotatingOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
)
922 || m_aAxisProperties
.m_bTryStaggeringFirst
)
925 rAxisLabelProperties
.m_eStaggering
= AxisLabelStaggering::StaggerEven
;
926 pLastVisibleNeighbourTickInfo
= pPREPreviousVisibleTickInfo
;
927 if( !pLastVisibleNeighbourTickInfo
||
928 !lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo
->xTextShape
929 , rAxisLabelProperties
.m_fRotationAngleDegree
930 , pTickInfo
->aTickScreenPosition
) )
931 bOverlapsAfterAutoStagger
= false;
935 if (bOverlapsAfterAutoStagger
)
937 // Staggering didn't solve the overlap.
938 if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
940 // Try auto-rotating the labels at 45 degrees and
941 // start over. This rotation angle will be stored for
942 // all future text shape creation runs.
943 // The nRhythm parameter is reset to 1 since the layout
944 // used for text labels is changed.
945 rAxisLabelProperties
.autoRotate45();
946 m_aAxisLabelProperties
.m_fRotationAngleDegree
= rAxisLabelProperties
.m_fRotationAngleDegree
; // Store it for future runs.
947 removeTextShapesFromTicks();
948 rAxisLabelProperties
.m_nRhythm
= 1;
952 // Try incrementing the tick interval and start over.
953 rAxisLabelProperties
.m_nRhythm
++;
954 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.m_nRhythm
, nTick
, xTarget
);
960 pPREPreviousVisibleTickInfo
= pPreviousVisibleTickInfo
;
961 pPreviousVisibleTickInfo
= pTickInfo
;
966 bool VCartesianAxis::createTextShapesSimple(
967 const rtl::Reference
< SvxShapeGroupAnyD
>& xTarget
, TickIter
& rTickIter
,
968 AxisLabelProperties
& rAxisLabelProperties
, TickFactory2D
const * pTickFactory
)
970 FixedNumberFormatter
aFixedNumberFormatter(
971 m_xNumberFormatsSupplier
, rAxisLabelProperties
.m_nNumberFormatKey
);
973 const bool bIsHorizontalAxis
= pTickFactory
->isHorizontalAxis();
974 const bool bIsVerticalAxis
= pTickFactory
->isVerticalAxis();
975 B2DVector aTextToTickDistance
= pTickFactory
->getDistanceAxisTickToText(m_aAxisProperties
, true);
977 // Stores an array of text label strings in case of a normal
978 // (non-complex) category axis.
979 const uno::Sequence
<OUString
>* pCategories
= nullptr;
980 if( m_bUseTextLabels
&& !m_aAxisProperties
.m_bComplexCategories
)
981 pCategories
= &m_aTextLabels
;
983 bool bLimitedHeight
= fabs(aTextToTickDistance
.getX()) > fabs(aTextToTickDistance
.getY());
985 //prepare properties for multipropertyset-interface of shape
986 tNameSequence aPropNames
;
987 tAnySequence aPropValues
;
988 getAxisLabelProperties(aPropNames
, aPropValues
, m_aAxisProperties
, rAxisLabelProperties
, -1, bLimitedHeight
);
990 uno::Any
* pColorAny
= PropertyMapper::getValuePointer(aPropValues
,aPropNames
,u
"CharColor");
991 Color nColor
= COL_AUTO
;
993 *pColorAny
>>= nColor
;
995 uno::Any
* pLimitedSpaceAny
= PropertyMapper::getValuePointerForLimitedSpace(aPropValues
,aPropNames
,bLimitedHeight
);
997 const TickInfo
* pPreviousVisibleTickInfo
= nullptr;
999 for( TickInfo
* pTickInfo
= rTickIter
.firstInfo()
1001 ; pTickInfo
= rTickIter
.nextInfo(), nTick
++ )
1003 const TickInfo
* pLastVisibleNeighbourTickInfo
= pPreviousVisibleTickInfo
;
1005 //don't create labels which does not fit into the rhythm
1006 if( nTick
%rAxisLabelProperties
.m_nRhythm
!= 0 )
1009 //don't create labels for invisible ticks
1010 if( !pTickInfo
->bPaintIt
)
1013 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.m_bOverlapAllowed
)
1015 // Overlapping is not allowed. If the label overlaps with its
1016 // neighboring label, try increasing the tick interval (or rhythm
1017 // as it's called) and start over.
1019 if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo
->xTextShape
1020 , rAxisLabelProperties
.m_fRotationAngleDegree
1021 , pTickInfo
->aTickScreenPosition
) )
1023 // This tick overlaps with its neighbor. Increment the visible
1024 // tick intervals (if that's allowed) and start over.
1026 rAxisLabelProperties
.m_nRhythm
++;
1027 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.m_nRhythm
, nTick
, xTarget
);
1032 bool bHasExtraColor
=false;
1035 OUString aLabel
= getTextLabelString(
1036 aFixedNumberFormatter
, pCategories
, pTickInfo
, isComplexCategoryAxis(),
1037 nExtraColor
, bHasExtraColor
);
1040 *pColorAny
<<= bHasExtraColor
?nExtraColor
:nColor
;
1041 if(pLimitedSpaceAny
)
1042 *pLimitedSpaceAny
<<= sal_Int32(-1*pTickInfo
->nFactorForLimitedTextWidth
);
1044 B2DVector aTickScreenPos2D
= pTickInfo
->aTickScreenPosition
;
1045 aTickScreenPos2D
+= aTextToTickDistance
;
1046 awt::Point
aAnchorScreenPosition2D(
1047 static_cast<sal_Int32
>(aTickScreenPos2D
.getX())
1048 ,static_cast<sal_Int32
>(aTickScreenPos2D
.getY()));
1050 //create single label
1051 if(!pTickInfo
->xTextShape
.is())
1052 pTickInfo
->xTextShape
= createSingleLabel( xTarget
1053 , aAnchorScreenPosition2D
, aLabel
1054 , rAxisLabelProperties
, m_aAxisProperties
1055 , aPropNames
, aPropValues
, bIsHorizontalAxis
);
1056 if(!pTickInfo
->xTextShape
.is())
1059 recordMaximumTextSize( *pTickInfo
->xTextShape
, rAxisLabelProperties
.m_fRotationAngleDegree
);
1061 //if NO OVERLAP -> remove overlapping shapes
1062 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.m_bOverlapAllowed
)
1064 // Check if the label still overlaps with its neighbor.
1065 if( doesOverlap( pLastVisibleNeighbourTickInfo
->xTextShape
, pTickInfo
->xTextShape
, rAxisLabelProperties
.m_fRotationAngleDegree
) )
1068 if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
1070 // Try auto-rotating the labels at 45 degrees and
1071 // start over. This rotation angle will be stored for
1072 // all future text shape creation runs.
1073 // The nRhythm parameter is reset to 1 since the layout
1074 // used for text labels is changed.
1075 rAxisLabelProperties
.autoRotate45();
1076 m_aAxisLabelProperties
.m_fRotationAngleDegree
= rAxisLabelProperties
.m_fRotationAngleDegree
; // Store it for future runs.
1077 removeTextShapesFromTicks();
1078 rAxisLabelProperties
.m_nRhythm
= 1;
1082 // Try incrementing the tick interval and start over.
1083 rAxisLabelProperties
.m_nRhythm
++;
1084 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.m_nRhythm
, nTick
, xTarget
);
1089 pPreviousVisibleTickInfo
= pTickInfo
;
1094 double VCartesianAxis::getAxisIntersectionValue() const
1096 if (m_aAxisProperties
.m_pfMainLinePositionAtOtherAxis
)
1097 return *m_aAxisProperties
.m_pfMainLinePositionAtOtherAxis
;
1099 double fMin
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMinY();
1100 double fMax
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMaxY();
1102 return (m_aAxisProperties
.m_eCrossoverType
== css::chart::ChartAxisPosition_END
) ? fMax
: fMin
;
1105 double VCartesianAxis::getLabelLineIntersectionValue() const
1107 if (m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_START
)
1108 return (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMinY();
1110 if (m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_END
)
1111 return (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMaxY();
1113 return getAxisIntersectionValue();
1116 double VCartesianAxis::getExtraLineIntersectionValue() const
1118 if( !m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
)
1119 return std::numeric_limits
<double>::quiet_NaN();
1121 double fMin
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMinY();
1122 double fMax
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMaxY();
1124 if( *m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
<= fMin
1125 || *m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
>= fMax
)
1126 return std::numeric_limits
<double>::quiet_NaN();
1128 return *m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
;
1131 B2DVector
VCartesianAxis::getScreenPosition( double fLogicX
, double fLogicY
, double fLogicZ
) const
1133 B2DVector
aRet(0,0);
1137 drawing::Position3D aScenePos
= m_pPosHelper
->transformLogicToScene( fLogicX
, fLogicY
, fLogicZ
, true );
1140 if (m_xLogicTarget
.is())
1142 tPropertyNameMap aDummyPropertyNameMap
;
1143 rtl::Reference
<Svx3DExtrudeObject
> xShape3DAnchor
= ShapeFactory::createCube( m_xLogicTarget
1144 , aScenePos
,drawing::Direction3D(1,1,1), 0, nullptr, aDummyPropertyNameMap
);
1145 awt::Point a2DPos
= xShape3DAnchor
->getPosition(); //get 2D position from xShape3DAnchor
1146 m_xLogicTarget
->remove(xShape3DAnchor
);
1147 aRet
.setX( a2DPos
.X
);
1148 aRet
.setY( a2DPos
.Y
);
1152 OSL_FAIL("cannot calculate screen position in VCartesianAxis::getScreenPosition");
1157 aRet
.setX( aScenePos
.PositionX
);
1158 aRet
.setY( aScenePos
.PositionY
);
1165 VCartesianAxis::ScreenPosAndLogicPos
VCartesianAxis::getScreenPosAndLogicPos( double fLogicX_
, double fLogicY_
, double fLogicZ_
) const
1167 ScreenPosAndLogicPos aRet
;
1168 aRet
.fLogicX
= fLogicX_
;
1169 aRet
.fLogicY
= fLogicY_
;
1170 aRet
.fLogicZ
= fLogicZ_
;
1171 aRet
.aScreenPos
= getScreenPosition( fLogicX_
, fLogicY_
, fLogicZ_
);
1175 typedef std::vector
< VCartesianAxis::ScreenPosAndLogicPos
> tScreenPosAndLogicPosList
;
1181 bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos
& rPos1
, const VCartesianAxis::ScreenPosAndLogicPos
& rPos2
)
1183 return ( rPos1
.aScreenPos
.getX() < rPos2
.aScreenPos
.getX() );
1187 struct lcl_GreaterYPos
1189 bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos
& rPos1
, const VCartesianAxis::ScreenPosAndLogicPos
& rPos2
)
1191 return ( rPos1
.aScreenPos
.getY() > rPos2
.aScreenPos
.getY() );
1197 void VCartesianAxis::get2DAxisMainLine(
1198 B2DVector
& rStart
, B2DVector
& rEnd
, AxisLabelAlignment
& rAlignment
, double fCrossesOtherAxis
) const
1200 //m_aAxisProperties might get updated and changed here because
1201 // the label alignment and inner direction sign depends exactly of the choice of the axis line position which is made here in this method
1203 double const fMinX
= m_pPosHelper
->getLogicMinX();
1204 double const fMinY
= m_pPosHelper
->getLogicMinY();
1205 double const fMinZ
= m_pPosHelper
->getLogicMinZ();
1206 double const fMaxX
= m_pPosHelper
->getLogicMaxX();
1207 double const fMaxY
= m_pPosHelper
->getLogicMaxY();
1208 double const fMaxZ
= m_pPosHelper
->getLogicMaxZ();
1210 double fXOnXPlane
= fMinX
;
1211 double fXOther
= fMaxX
;
1212 int nDifferentValue
= !m_pPosHelper
->isMathematicalOrientationX() ? -1 : 1;
1213 if( !m_pPosHelper
->isSwapXAndY() )
1214 nDifferentValue
*= (m_eLeftWallPos
!= CuboidPlanePosition_Left
) ? -1 : 1;
1216 nDifferentValue
*= (m_eBottomPos
!= CuboidPlanePosition_Bottom
) ? -1 : 1;
1217 if( nDifferentValue
<0 )
1223 double fYOnYPlane
= fMinY
;
1224 double fYOther
= fMaxY
;
1225 nDifferentValue
= !m_pPosHelper
->isMathematicalOrientationY() ? -1 : 1;
1226 if( !m_pPosHelper
->isSwapXAndY() )
1227 nDifferentValue
*= (m_eBottomPos
!= CuboidPlanePosition_Bottom
) ? -1 : 1;
1229 nDifferentValue
*= (m_eLeftWallPos
!= CuboidPlanePosition_Left
) ? -1 : 1;
1230 if( nDifferentValue
<0 )
1236 double fZOnZPlane
= fMaxZ
;
1237 double fZOther
= fMinZ
;
1238 nDifferentValue
= !m_pPosHelper
->isMathematicalOrientationZ() ? -1 : 1;
1239 nDifferentValue
*= (m_eBackWallPos
!= CuboidPlanePosition_Back
) ? -1 : 1;
1240 if( nDifferentValue
<0 )
1246 double fXStart
= fMinX
;
1247 double fYStart
= fMinY
;
1248 double fZStart
= fMinZ
;
1251 double fZEnd
= fZStart
;
1253 if( m_nDimensionIndex
==0 ) //x-axis
1255 if( fCrossesOtherAxis
< fMinY
)
1256 fCrossesOtherAxis
= fMinY
;
1257 else if( fCrossesOtherAxis
> fMaxY
)
1258 fCrossesOtherAxis
= fMaxY
;
1260 fYStart
= fYEnd
= fCrossesOtherAxis
;
1261 fXEnd
=m_pPosHelper
->getLogicMaxX();
1265 if( AxisHelper::isAxisPositioningEnabled() )
1267 if( ::rtl::math::approxEqual( fYOther
, fYStart
) )
1268 fZStart
= fZEnd
= fZOnZPlane
;
1270 fZStart
= fZEnd
= fZOther
;
1274 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1275 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1277 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1278 double fDeltaY
= rEnd
.getY() - rStart
.getY();
1280 //only those points are candidates which are lying on exactly one wall as these are outer edges
1281 tScreenPosAndLogicPosList aPosList
{ getScreenPosAndLogicPos( fMinX
, fYOnYPlane
, fZOther
), getScreenPosAndLogicPos( fMinX
, fYOther
, fZOnZPlane
) };
1283 if( fabs(fDeltaY
) > fabs(fDeltaX
) )
1285 rAlignment
.meAlignment
= LABEL_ALIGN_LEFT
;
1286 //choose most left positions
1287 std::sort( aPosList
.begin(), aPosList
.end(), lcl_LessXPos() );
1288 rAlignment
.mfLabelDirection
= (fDeltaY
< 0) ? -1.0 : 1.0;
1292 rAlignment
.meAlignment
= LABEL_ALIGN_BOTTOM
;
1293 //choose most bottom positions
1294 std::sort( aPosList
.begin(), aPosList
.end(), lcl_GreaterYPos() );
1295 rAlignment
.mfLabelDirection
= (fDeltaX
< 0) ? -1.0 : 1.0;
1297 ScreenPosAndLogicPos
aBestPos( aPosList
[0] );
1298 fYStart
= fYEnd
= aBestPos
.fLogicY
;
1299 fZStart
= fZEnd
= aBestPos
.fLogicZ
;
1300 if( !m_pPosHelper
->isMathematicalOrientationX() )
1301 rAlignment
.mfLabelDirection
*= -1.0;
1305 else if( m_nDimensionIndex
==1 ) //y-axis
1307 if( fCrossesOtherAxis
< fMinX
)
1308 fCrossesOtherAxis
= fMinX
;
1309 else if( fCrossesOtherAxis
> fMaxX
)
1310 fCrossesOtherAxis
= fMaxX
;
1312 fXStart
= fXEnd
= fCrossesOtherAxis
;
1313 fYEnd
=m_pPosHelper
->getLogicMaxY();
1317 if( AxisHelper::isAxisPositioningEnabled() )
1319 if( ::rtl::math::approxEqual( fXOther
, fXStart
) )
1320 fZStart
= fZEnd
= fZOnZPlane
;
1322 fZStart
= fZEnd
= fZOther
;
1326 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1327 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1329 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1330 double fDeltaY
= rEnd
.getY() - rStart
.getY();
1332 //only those points are candidates which are lying on exactly one wall as these are outer edges
1333 tScreenPosAndLogicPosList aPosList
{ getScreenPosAndLogicPos( fXOnXPlane
, fMinY
, fZOther
), getScreenPosAndLogicPos( fXOther
, fMinY
, fZOnZPlane
) };
1335 if( fabs(fDeltaY
) > fabs(fDeltaX
) )
1337 rAlignment
.meAlignment
= LABEL_ALIGN_LEFT
;
1338 //choose most left positions
1339 std::sort( aPosList
.begin(), aPosList
.end(), lcl_LessXPos() );
1340 rAlignment
.mfLabelDirection
= (fDeltaY
< 0) ? -1.0 : 1.0;
1344 rAlignment
.meAlignment
= LABEL_ALIGN_BOTTOM
;
1345 //choose most bottom positions
1346 std::sort( aPosList
.begin(), aPosList
.end(), lcl_GreaterYPos() );
1347 rAlignment
.mfLabelDirection
= (fDeltaX
< 0) ? -1.0 : 1.0;
1349 ScreenPosAndLogicPos
aBestPos( aPosList
[0] );
1350 fXStart
= fXEnd
= aBestPos
.fLogicX
;
1351 fZStart
= fZEnd
= aBestPos
.fLogicZ
;
1352 if( !m_pPosHelper
->isMathematicalOrientationY() )
1353 rAlignment
.mfLabelDirection
*= -1.0;
1359 fZEnd
= m_pPosHelper
->getLogicMaxZ();
1360 if( AxisHelper::isAxisPositioningEnabled() )
1362 if( !m_aAxisProperties
.m_bSwapXAndY
)
1364 if( fCrossesOtherAxis
< fMinY
)
1365 fCrossesOtherAxis
= fMinY
;
1366 else if( fCrossesOtherAxis
> fMaxY
)
1367 fCrossesOtherAxis
= fMaxY
;
1368 fYStart
= fYEnd
= fCrossesOtherAxis
;
1370 if( ::rtl::math::approxEqual( fYOther
, fYStart
) )
1371 fXStart
= fXEnd
= fXOnXPlane
;
1373 fXStart
= fXEnd
= fXOther
;
1377 if( fCrossesOtherAxis
< fMinX
)
1378 fCrossesOtherAxis
= fMinX
;
1379 else if( fCrossesOtherAxis
> fMaxX
)
1380 fCrossesOtherAxis
= fMaxX
;
1381 fXStart
= fXEnd
= fCrossesOtherAxis
;
1383 if( ::rtl::math::approxEqual( fXOther
, fXStart
) )
1384 fYStart
= fYEnd
= fYOnYPlane
;
1386 fYStart
= fYEnd
= fYOther
;
1391 if( !m_pPosHelper
->isSwapXAndY() )
1393 fXStart
= fXEnd
= m_pPosHelper
->isMathematicalOrientationX() ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMinX();
1394 fYStart
= fYEnd
= m_pPosHelper
->isMathematicalOrientationY() ? m_pPosHelper
->getLogicMinY() : m_pPosHelper
->getLogicMaxY();
1398 fXStart
= fXEnd
= m_pPosHelper
->isMathematicalOrientationX() ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMaxX();
1399 fYStart
= fYEnd
= m_pPosHelper
->isMathematicalOrientationY() ? m_pPosHelper
->getLogicMaxY() : m_pPosHelper
->getLogicMinY();
1404 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1405 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1407 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1409 //only those points are candidates which are lying on exactly one wall as these are outer edges
1410 tScreenPosAndLogicPosList aPosList
{ getScreenPosAndLogicPos( fXOther
, fYOnYPlane
, fMinZ
), getScreenPosAndLogicPos( fXOnXPlane
, fYOther
, fMinZ
) };
1412 std::sort( aPosList
.begin(), aPosList
.end(), lcl_GreaterYPos() );
1413 ScreenPosAndLogicPos
aBestPos( aPosList
[0] );
1414 ScreenPosAndLogicPos
aNotSoGoodPos( aPosList
[1] );
1416 //choose most bottom positions
1417 if( fDeltaX
!= 0.0 ) // prefer left-right alignments
1419 if( aBestPos
.aScreenPos
.getX() > aNotSoGoodPos
.aScreenPos
.getX() )
1420 rAlignment
.meAlignment
= LABEL_ALIGN_RIGHT
;
1422 rAlignment
.meAlignment
= LABEL_ALIGN_LEFT
;
1426 if( aBestPos
.aScreenPos
.getY() > aNotSoGoodPos
.aScreenPos
.getY() )
1427 rAlignment
.meAlignment
= LABEL_ALIGN_BOTTOM
;
1429 rAlignment
.meAlignment
= LABEL_ALIGN_TOP
;
1432 rAlignment
.mfLabelDirection
= (fDeltaX
< 0) ? -1.0 : 1.0;
1433 if( !m_pPosHelper
->isMathematicalOrientationZ() )
1434 rAlignment
.mfLabelDirection
*= -1.0;
1436 fXStart
= fXEnd
= aBestPos
.fLogicX
;
1437 fYStart
= fYEnd
= aBestPos
.fLogicY
;
1442 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1443 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1445 if(m_nDimension
==3 && !AxisHelper::isAxisPositioningEnabled() )
1446 rAlignment
.mfInnerTickDirection
= rAlignment
.mfLabelDirection
;//to behave like before
1448 if(!(m_nDimension
==3 && AxisHelper::isAxisPositioningEnabled()) )
1451 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1452 double fDeltaY
= rEnd
.getY() - rStart
.getY();
1454 if( m_nDimensionIndex
==2 )
1456 if( m_eLeftWallPos
!= CuboidPlanePosition_Left
)
1458 rAlignment
.mfLabelDirection
*= -1.0;
1459 rAlignment
.mfInnerTickDirection
*= -1.0;
1462 rAlignment
.meAlignment
=
1463 (rAlignment
.mfLabelDirection
< 0) ?
1464 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1466 if( ( fDeltaY
<0 && m_aScale
.Orientation
== chart2::AxisOrientation_REVERSE
) ||
1467 ( fDeltaY
>0 && m_aScale
.Orientation
== chart2::AxisOrientation_MATHEMATICAL
) )
1468 rAlignment
.meAlignment
=
1469 (rAlignment
.meAlignment
== LABEL_ALIGN_RIGHT
) ?
1470 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1472 else if( fabs(fDeltaY
) > fabs(fDeltaX
) )
1474 if( m_eBackWallPos
!= CuboidPlanePosition_Back
)
1476 rAlignment
.mfLabelDirection
*= -1.0;
1477 rAlignment
.mfInnerTickDirection
*= -1.0;
1480 rAlignment
.meAlignment
=
1481 (rAlignment
.mfLabelDirection
< 0) ?
1482 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1484 if( ( fDeltaY
<0 && m_aScale
.Orientation
== chart2::AxisOrientation_REVERSE
) ||
1485 ( fDeltaY
>0 && m_aScale
.Orientation
== chart2::AxisOrientation_MATHEMATICAL
) )
1486 rAlignment
.meAlignment
=
1487 (rAlignment
.meAlignment
== LABEL_ALIGN_RIGHT
) ?
1488 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1492 if( m_eBackWallPos
!= CuboidPlanePosition_Back
)
1494 rAlignment
.mfLabelDirection
*= -1.0;
1495 rAlignment
.mfInnerTickDirection
*= -1.0;
1498 rAlignment
.meAlignment
=
1499 (rAlignment
.mfLabelDirection
< 0) ?
1500 LABEL_ALIGN_TOP
: LABEL_ALIGN_BOTTOM
;
1502 if( ( fDeltaX
>0 && m_aScale
.Orientation
== chart2::AxisOrientation_REVERSE
) ||
1503 ( fDeltaX
<0 && m_aScale
.Orientation
== chart2::AxisOrientation_MATHEMATICAL
) )
1504 rAlignment
.meAlignment
=
1505 (rAlignment
.meAlignment
== LABEL_ALIGN_TOP
) ?
1506 LABEL_ALIGN_BOTTOM
: LABEL_ALIGN_TOP
;
1510 TickFactory
* VCartesianAxis::createTickFactory()
1512 return createTickFactory2D();
1515 TickFactory2D
* VCartesianAxis::createTickFactory2D()
1517 AxisLabelAlignment aLabelAlign
= m_aAxisProperties
.maLabelAlignment
;
1518 B2DVector aStart
, aEnd
;
1519 get2DAxisMainLine(aStart
, aEnd
, aLabelAlign
, getAxisIntersectionValue());
1521 B2DVector aLabelLineStart
, aLabelLineEnd
;
1522 get2DAxisMainLine(aLabelLineStart
, aLabelLineEnd
, aLabelAlign
, getLabelLineIntersectionValue());
1523 m_aAxisProperties
.maLabelAlignment
= aLabelAlign
;
1525 return new TickFactory2D( m_aScale
, m_aIncrement
, aStart
, aEnd
, aLabelLineStart
-aStart
);
1528 static void lcl_hideIdenticalScreenValues( TickIter
& rTickIter
)
1530 TickInfo
* pPrevTickInfo
= rTickIter
.firstInfo();
1534 pPrevTickInfo
->bPaintIt
= true;
1535 for( TickInfo
* pTickInfo
= rTickIter
.nextInfo(); pTickInfo
; pTickInfo
= rTickIter
.nextInfo())
1537 pTickInfo
->bPaintIt
= (pTickInfo
->aTickScreenPosition
!= pPrevTickInfo
->aTickScreenPosition
);
1538 pPrevTickInfo
= pTickInfo
;
1542 //'hide' tickmarks with identical screen values in aAllTickInfos
1543 void VCartesianAxis::hideIdenticalScreenValues( TickInfoArraysType
& rTickInfos
) const
1545 if( isComplexCategoryAxis() || isDateAxis() )
1547 sal_Int32 nCount
= rTickInfos
.size();
1548 for( sal_Int32 nN
=0; nN
<nCount
; nN
++ )
1550 PureTickIter
aTickIter( rTickInfos
[nN
] );
1551 lcl_hideIdenticalScreenValues( aTickIter
);
1556 EquidistantTickIter
aTickIter( rTickInfos
, m_aIncrement
, -1 );
1557 lcl_hideIdenticalScreenValues( aTickIter
);
1561 sal_Int32
VCartesianAxis::estimateMaximumAutoMainIncrementCount()
1563 sal_Int32 nRet
= 10;
1565 if( m_nMaximumTextWidthSoFar
==0 && m_nMaximumTextHeightSoFar
==0 )
1568 B2DVector aStart
, aEnd
;
1569 AxisLabelAlignment aLabelAlign
= m_aAxisProperties
.maLabelAlignment
;
1570 get2DAxisMainLine(aStart
, aEnd
, aLabelAlign
, getAxisIntersectionValue());
1571 m_aAxisProperties
.maLabelAlignment
= aLabelAlign
;
1573 sal_Int32 nMaxHeight
= static_cast<sal_Int32
>(fabs(aEnd
.getY()-aStart
.getY()));
1574 sal_Int32 nMaxWidth
= static_cast<sal_Int32
>(fabs(aEnd
.getX()-aStart
.getX()));
1576 sal_Int32 nTotalAvailable
= nMaxHeight
;
1577 sal_Int32 nSingleNeeded
= m_nMaximumTextHeightSoFar
;
1578 sal_Int32 nMaxSameLabel
= 0;
1580 // tdf#48041: do not duplicate the value labels because of rounding
1581 if (m_aAxisProperties
.m_nAxisType
!= css::chart2::AxisType::DATE
)
1583 FixedNumberFormatter
aFixedNumberFormatterTest(m_xNumberFormatsSupplier
, m_aAxisLabelProperties
.m_nNumberFormatKey
);
1584 OUString sPreviousValueLabel
;
1585 sal_Int32 nSameLabel
= 0;
1586 for (auto const & nLabel
: m_aAllTickInfos
[0])
1588 Color nColor
= COL_AUTO
;
1589 bool bHasColor
= false;
1590 OUString sValueLabel
= aFixedNumberFormatterTest
.getFormattedString(nLabel
.fScaledTickValue
, nColor
, bHasColor
);
1591 if (sValueLabel
== sPreviousValueLabel
)
1594 if (nSameLabel
> nMaxSameLabel
)
1595 nMaxSameLabel
= nSameLabel
;
1599 sPreviousValueLabel
= sValueLabel
;
1602 //for horizontal axis:
1603 if( (m_nDimensionIndex
== 0 && !m_aAxisProperties
.m_bSwapXAndY
)
1604 || (m_nDimensionIndex
== 1 && m_aAxisProperties
.m_bSwapXAndY
) )
1606 nTotalAvailable
= nMaxWidth
;
1607 nSingleNeeded
= m_nMaximumTextWidthSoFar
;
1610 if( nSingleNeeded
>0 )
1611 nRet
= nTotalAvailable
/nSingleNeeded
;
1613 if ( nMaxSameLabel
> 0 )
1615 sal_Int32 nRetNoSameLabel
= m_aAllTickInfos
[0].size() / (nMaxSameLabel
+ 1);
1616 if ( nRet
> nRetNoSameLabel
)
1617 nRet
= nRetNoSameLabel
;
1623 void VCartesianAxis::doStaggeringOfLabels( const AxisLabelProperties
& rAxisLabelProperties
, TickFactory2D
const * pTickFactory2D
)
1625 if( !pTickFactory2D
)
1628 if( isComplexCategoryAxis() )
1630 sal_Int32 nTextLevelCount
= getTextLevelCount();
1631 B2DVector
aCumulatedLabelsDistance(0,0);
1632 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1634 std::unique_ptr
<TickIter
> apTickIter(createLabelTickIterator(nTextLevel
));
1637 double fRotationAngleDegree
= m_aAxisLabelProperties
.m_fRotationAngleDegree
;
1640 lcl_shiftLabels(*apTickIter
, aCumulatedLabelsDistance
);
1641 //multilevel labels: 0 or 90 by default
1642 if( m_aAxisProperties
.m_bSwapXAndY
)
1643 fRotationAngleDegree
= 90.0;
1645 fRotationAngleDegree
= 0.0;
1647 aCumulatedLabelsDistance
+= lcl_getLabelsDistance(
1648 *apTickIter
, pTickFactory2D
->getDistanceAxisTickToText(m_aAxisProperties
),
1649 fRotationAngleDegree
);
1653 else if (rAxisLabelProperties
.isStaggered())
1655 if( !m_aAllTickInfos
.empty() )
1657 LabelIterator
aInnerIter( m_aAllTickInfos
[0], rAxisLabelProperties
.m_eStaggering
, true );
1658 LabelIterator
aOuterIter( m_aAllTickInfos
[0], rAxisLabelProperties
.m_eStaggering
, false );
1660 lcl_shiftLabels( aOuterIter
1661 , lcl_getLabelsDistance( aInnerIter
1662 , pTickFactory2D
->getDistanceAxisTickToText( m_aAxisProperties
), 0.0 ) );
1667 void VCartesianAxis::createDataTableShape(std::unique_ptr
<TickFactory2D
> const& rpTickFactory2D
)
1669 // Check if we can create the data table shape
1670 // Data table view and m_bDisplayDataTable must be true
1671 if (!m_pDataTableView
|| !m_aAxisProperties
.m_bDisplayDataTable
)
1674 m_pDataTableView
->initializeShapes(m_xDataTableTarget
);
1675 basegfx::B2DVector aStart
= rpTickFactory2D
->getXaxisStartPos();
1676 basegfx::B2DVector aEnd
= rpTickFactory2D
->getXaxisEndPos();
1678 rpTickFactory2D
->updateScreenValues(m_aAllTickInfos
);
1680 sal_Int32 nDistance
= -1;
1682 std::unique_ptr
<TickIter
> apTickIter(createLabelTickIterator(0));
1685 nDistance
= TickFactory2D::getTickScreenDistance(*apTickIter
);
1686 if (getTextLevelCount() > 1)
1692 // we only have one data series so we have no TickMarks, therefore calculate and use the table size
1693 auto rDelta
= aEnd
- aStart
;
1694 nDistance
= basegfx::fround(rDelta
.getX());
1699 m_pDataTableView
->createShapes(aStart
, aEnd
, nDistance
);
1703 void VCartesianAxis::createLabels()
1705 if( !prepareShapeCreation() )
1708 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1710 createDataTableShape(apTickFactory2D
);
1713 if (!m_aAxisProperties
.m_bDisplayLabels
)
1716 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1718 //get the transformed screen values for all tickmarks in aAllTickInfos
1719 pTickFactory2D
->updateScreenValues( m_aAllTickInfos
);
1720 //'hide' tickmarks with identical screen values in aAllTickInfos
1721 hideIdenticalScreenValues( m_aAllTickInfos
);
1723 removeTextShapesFromTicks();
1725 //create tick mark text shapes
1726 sal_Int32 nTextLevelCount
= getTextLevelCount();
1727 sal_Int32 nScreenDistanceBetweenTicks
= -1;
1728 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1730 std::unique_ptr
< TickIter
> apTickIter(createLabelTickIterator( nTextLevel
));
1735 nScreenDistanceBetweenTicks
= TickFactory2D::getTickScreenDistance(*apTickIter
);
1736 if( nTextLevelCount
>1 )
1737 nScreenDistanceBetweenTicks
*=2; //the above used tick iter does contain also the sub ticks -> thus the given distance is only the half
1740 AxisLabelProperties
aComplexProps(m_aAxisLabelProperties
);
1741 if( m_aAxisProperties
.m_bComplexCategories
)
1743 aComplexProps
.m_bLineBreakAllowed
= true;
1744 aComplexProps
.m_bOverlapAllowed
= aComplexProps
.m_fRotationAngleDegree
!= 0.0;
1745 if( nTextLevel
> 0 )
1747 //multilevel labels: 0 or 90 by default
1748 if( m_aAxisProperties
.m_bSwapXAndY
)
1749 aComplexProps
.m_fRotationAngleDegree
= 90.0;
1751 aComplexProps
.m_fRotationAngleDegree
= 0.0;
1754 AxisLabelProperties
& rAxisLabelProperties
= m_aAxisProperties
.m_bComplexCategories
? aComplexProps
: m_aAxisLabelProperties
;
1755 while (!createTextShapes(m_xTextTarget
, *apTickIter
, rAxisLabelProperties
,
1756 pTickFactory2D
, nScreenDistanceBetweenTicks
))
1761 doStaggeringOfLabels( m_aAxisLabelProperties
, pTickFactory2D
);
1763 if (m_pDataTableView
)
1765 sal_Int32 x
= m_xTextTarget
->getPosition().X
;
1766 sal_Int32 y
= m_xTextTarget
->getPosition().Y
;
1767 sal_Int32 height
= m_xTextTarget
->getSize().Height
;
1768 m_pDataTableView
->changePosition(x
, y
+ height
);
1772 void VCartesianAxis::createMaximumLabels()
1774 m_bRecordMaximumTextSize
= true;
1775 const comphelper::ScopeGuard
aGuard([this]() { m_bRecordMaximumTextSize
= false; });
1777 if( !prepareShapeCreation() )
1781 if (!m_aAxisProperties
.m_bDisplayLabels
)
1784 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1785 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1787 //get the transformed screen values for all tickmarks in aAllTickInfos
1788 pTickFactory2D
->updateScreenValues( m_aAllTickInfos
);
1790 //create tick mark text shapes
1791 //@todo: iterate through all tick depth which should be labeled
1793 AxisLabelProperties
aAxisLabelProperties( m_aAxisLabelProperties
);
1794 if( isAutoStaggeringOfLabelsAllowed( aAxisLabelProperties
, pTickFactory2D
->isHorizontalAxis(), pTickFactory2D
->isVerticalAxis() ) )
1795 aAxisLabelProperties
.m_eStaggering
= AxisLabelStaggering::StaggerEven
;
1797 aAxisLabelProperties
.m_bOverlapAllowed
= true;
1798 aAxisLabelProperties
.m_bLineBreakAllowed
= false;
1799 sal_Int32 nTextLevelCount
= getTextLevelCount();
1800 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1802 std::unique_ptr
< TickIter
> apTickIter(createMaximumLabelTickIterator( nTextLevel
));
1805 while (!createTextShapes(m_xTextTarget
, *apTickIter
, aAxisLabelProperties
,
1806 pTickFactory2D
, -1))
1811 doStaggeringOfLabels( aAxisLabelProperties
, pTickFactory2D
);
1814 void VCartesianAxis::updatePositions()
1816 //update positions of labels
1817 if (!m_aAxisProperties
.m_bDisplayLabels
)
1820 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1821 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1823 //update positions of all existing text shapes
1824 pTickFactory2D
->updateScreenValues( m_aAllTickInfos
);
1827 for (auto const& tickInfos
: m_aAllTickInfos
)
1829 for (auto const& tickInfo
: tickInfos
)
1831 const rtl::Reference
<SvxShapeText
> & xShape2DText(tickInfo
.xTextShape
);
1832 if( xShape2DText
.is() )
1834 B2DVector
aTextToTickDistance( pTickFactory2D
->getDistanceAxisTickToText( m_aAxisProperties
, true ) );
1835 B2DVector
aTickScreenPos2D(tickInfo
.aTickScreenPosition
);
1836 aTickScreenPos2D
+= aTextToTickDistance
;
1837 awt::Point
aAnchorScreenPosition2D(
1838 static_cast<sal_Int32
>(aTickScreenPos2D
.getX())
1839 ,static_cast<sal_Int32
>(aTickScreenPos2D
.getY()));
1841 double fRotationAngleDegree
= m_aAxisLabelProperties
.m_fRotationAngleDegree
;
1844 //multilevel labels: 0 or 90 by default
1845 if( pTickFactory2D
->isHorizontalAxis() )
1846 fRotationAngleDegree
= 0.0;
1848 fRotationAngleDegree
= 90;
1851 // #i78696# use mathematically correct rotation now
1852 const double fRotationAnglePi(-basegfx::deg2rad(fRotationAngleDegree
));
1853 uno::Any aATransformation
= ShapeFactory::makeTransformation(aAnchorScreenPosition2D
, fRotationAnglePi
);
1858 xShape2DText
->SvxShape::setPropertyValue( u
"Transformation"_ustr
, aATransformation
);
1860 catch( const uno::Exception
& )
1862 TOOLS_WARN_EXCEPTION("chart2", "" );
1865 //correctPositionForRotation
1866 LabelPositionHelper::correctPositionForRotation( xShape2DText
1867 , m_aAxisProperties
.maLabelAlignment
.meAlignment
, fRotationAngleDegree
, m_aAxisProperties
.m_bComplexCategories
);
1873 doStaggeringOfLabels( m_aAxisLabelProperties
, pTickFactory2D
);
1876 void VCartesianAxis::createTickMarkLineShapes( TickInfoArrayType
& rTickInfos
, const TickmarkProperties
& rTickmarkProperties
, TickFactory2D
const & rTickFactory2D
, bool bOnlyAtLabels
)
1878 sal_Int32 nPointCount
= rTickInfos
.size();
1879 drawing::PointSequenceSequence
aPoints(2*nPointCount
);
1882 for (auto const& tickInfo
: rTickInfos
)
1884 if( !tickInfo
.bPaintIt
)
1887 bool bTicksAtLabels
= ( m_aAxisProperties
.m_eTickmarkPos
!= css::chart::ChartAxisMarkPosition_AT_AXIS
);
1888 double fInnerDirectionSign
= m_aAxisProperties
.maLabelAlignment
.mfInnerTickDirection
;
1889 if( bTicksAtLabels
&& m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_END
)
1890 fInnerDirectionSign
*= -1.0;
1891 bTicksAtLabels
= bTicksAtLabels
|| bOnlyAtLabels
;
1892 //add ticks at labels:
1893 rTickFactory2D
.addPointSequenceForTickLine( aPoints
, nN
++, tickInfo
.fScaledTickValue
1894 , fInnerDirectionSign
, rTickmarkProperties
, bTicksAtLabels
);
1895 //add ticks at axis (without labels):
1896 if( !bOnlyAtLabels
&& m_aAxisProperties
.m_eTickmarkPos
== css::chart::ChartAxisMarkPosition_AT_LABELS_AND_AXIS
)
1897 rTickFactory2D
.addPointSequenceForTickLine( aPoints
, nN
++, tickInfo
.fScaledTickValue
1898 , m_aAxisProperties
.maLabelAlignment
.mfInnerTickDirection
, rTickmarkProperties
, !bTicksAtLabels
);
1900 aPoints
.realloc(nN
);
1901 ShapeFactory::createLine2D( m_xGroupShape_Shapes
, aPoints
1902 , &rTickmarkProperties
.aLineProperties
);
1905 void VCartesianAxis::createShapes()
1907 if( !prepareShapeCreation() )
1910 //create line shapes
1913 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1914 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1916 //create extra long ticks to separate complex categories (create them only there where the labels are)
1917 if( isComplexCategoryAxis() )
1919 TickInfoArraysType aComplexTickInfos
;
1920 createAllTickInfosFromComplexCategories( aComplexTickInfos
, true );
1921 pTickFactory2D
->updateScreenValues( aComplexTickInfos
);
1922 hideIdenticalScreenValues( aComplexTickInfos
);
1924 std::vector
<TickmarkProperties
> aTickmarkPropertiesList
;
1925 static const bool bIncludeSpaceBetweenTickAndText
= false;
1926 sal_Int32 nOffset
= static_cast<sal_Int32
>(pTickFactory2D
->getDistanceAxisTickToText( m_aAxisProperties
, false, bIncludeSpaceBetweenTickAndText
).getLength());
1927 sal_Int32 nTextLevelCount
= getTextLevelCount();
1928 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1930 std::unique_ptr
< TickIter
> apTickIter(createLabelTickIterator( nTextLevel
));
1933 double fRotationAngleDegree
= m_aAxisLabelProperties
.m_fRotationAngleDegree
;
1934 if( nTextLevel
> 0 )
1936 //Multi-level Labels: default to 0 or 90
1937 if( m_aAxisProperties
.m_bSwapXAndY
)
1938 fRotationAngleDegree
= 90.0;
1940 fRotationAngleDegree
= 0.0;
1942 B2DVector
aLabelsDistance(lcl_getLabelsDistance(
1943 *apTickIter
, pTickFactory2D
->getDistanceAxisTickToText(m_aAxisProperties
),
1944 fRotationAngleDegree
));
1945 sal_Int32 nCurrentLength
= static_cast<sal_Int32
>(aLabelsDistance
.getLength());
1946 aTickmarkPropertiesList
.push_back( m_aAxisProperties
.makeTickmarkPropertiesForComplexCategories( nOffset
+ nCurrentLength
, 0 ) );
1947 nOffset
+= nCurrentLength
;
1951 sal_Int32 nTickmarkPropertiesCount
= aTickmarkPropertiesList
.size();
1952 TickInfoArraysType::iterator aDepthIter
= aComplexTickInfos
.begin();
1953 const TickInfoArraysType::const_iterator aDepthEnd
= aComplexTickInfos
.end();
1954 for( sal_Int32 nDepth
=0; aDepthIter
!= aDepthEnd
&& nDepth
< nTickmarkPropertiesCount
; ++aDepthIter
, nDepth
++ )
1956 if(nDepth
==0 && !m_aAxisProperties
.m_nMajorTickmarks
)
1958 createTickMarkLineShapes( *aDepthIter
, aTickmarkPropertiesList
[nDepth
], *pTickFactory2D
, true /*bOnlyAtLabels*/ );
1961 //create normal ticks for major and minor intervals
1963 TickInfoArraysType aUnshiftedTickInfos
;
1964 if( m_aScale
.m_bShiftedCategoryPosition
)// if m_bShiftedCategoryPosition==true the tickmarks in m_aAllTickInfos are shifted
1966 pTickFactory2D
->getAllTicks( aUnshiftedTickInfos
);
1967 pTickFactory2D
->updateScreenValues( aUnshiftedTickInfos
);
1968 hideIdenticalScreenValues( aUnshiftedTickInfos
);
1970 TickInfoArraysType
& rAllTickInfos
= m_aScale
.m_bShiftedCategoryPosition
? aUnshiftedTickInfos
: m_aAllTickInfos
;
1972 if (rAllTickInfos
.empty())
1975 sal_Int32 nDepth
= 0;
1976 sal_Int32 nTickmarkPropertiesCount
= m_aAxisProperties
.m_aTickmarkPropertiesList
.size();
1977 for( auto& rTickInfos
: rAllTickInfos
)
1979 if (nDepth
== nTickmarkPropertiesCount
)
1982 createTickMarkLineShapes( rTickInfos
, m_aAxisProperties
.m_aTickmarkPropertiesList
[nDepth
], *pTickFactory2D
, false /*bOnlyAtLabels*/ );
1986 //create axis main lines
1987 //it serves also as the handle shape for the axis selection
1989 drawing::PointSequenceSequence
aPoints(1);
1990 apTickFactory2D
->createPointSequenceForAxisMainLine( aPoints
);
1991 rtl::Reference
<SvxShapePolyPolygon
> xShape
= ShapeFactory::createLine2D(
1992 m_xGroupShape_Shapes
, aPoints
1993 , &m_aAxisProperties
.m_aLineProperties
);
1994 //because of this name this line will be used for marking the axis
1995 ::chart::ShapeFactory::setShapeName( xShape
, u
"MarkHandles"_ustr
);
1997 //create an additional line at NULL
1998 if( !AxisHelper::isAxisPositioningEnabled() )
2000 double fExtraLineCrossesOtherAxis
= getExtraLineIntersectionValue();
2001 if (!std::isnan(fExtraLineCrossesOtherAxis
))
2003 B2DVector aStart
, aEnd
;
2004 AxisLabelAlignment aLabelAlign
= m_aAxisProperties
.maLabelAlignment
;
2005 get2DAxisMainLine(aStart
, aEnd
, aLabelAlign
, fExtraLineCrossesOtherAxis
);
2006 m_aAxisProperties
.maLabelAlignment
= aLabelAlign
;
2007 drawing::PointSequenceSequence aPoints
{{
2008 {static_cast<sal_Int32
>(aStart
.getX()), static_cast<sal_Int32
>(aStart
.getY())},
2009 {static_cast<sal_Int32
>(aEnd
.getX()), static_cast<sal_Int32
>(aEnd
.getY())} }};
2010 ShapeFactory::createLine2D(
2011 m_xGroupShape_Shapes
, aPoints
, &m_aAxisProperties
.m_aLineProperties
);
2019 void VCartesianAxis::createDataTableView(std::vector
<std::unique_ptr
<VSeriesPlotter
>>& rSeriesPlotterList
,
2020 Reference
<util::XNumberFormatsSupplier
> const& xNumberFormatsSupplier
,
2021 rtl::Reference
<::chart::ChartModel
> const& xChartDoc
,
2022 css::uno::Reference
<css::uno::XComponentContext
> const& rComponentContext
)
2024 if (!m_aAxisProperties
.m_bDisplayDataTable
)
2027 m_pDataTableView
.reset(new DataTableView(xChartDoc
, m_aAxisProperties
.m_xDataTableModel
, rComponentContext
, m_aAxisProperties
.m_bDataTableAlignAxisValuesWithColumns
));
2028 m_pDataTableView
->initializeValues(rSeriesPlotterList
);
2029 m_xNumberFormatsSupplier
= xNumberFormatsSupplier
;
2035 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */