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 <TrueGuard.hxx>
27 #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 <com/sun/star/chart2/XAxis.hpp>
33 #include <rtl/math.hxx>
34 #include <tools/diagnose_ex.h>
35 #include <tools/color.hxx>
36 #include <svx/unoshape.hxx>
37 #include <svx/unoshtxt.hxx>
39 #include <basegfx/polygon/b2dpolygon.hxx>
40 #include <basegfx/polygon/b2dpolypolygon.hxx>
41 #include <basegfx/polygon/b2dpolygontools.hxx>
42 #include <basegfx/polygon/b2dpolygonclipper.hxx>
43 #include <basegfx/matrix/b2dhommatrix.hxx>
44 #include <basegfx/numeric/ftools.hxx>
49 using namespace ::com::sun::star
;
50 using ::com::sun::star::uno::Reference
;
51 using ::basegfx::B2DVector
;
52 using ::basegfx::B2DPolygon
;
53 using ::basegfx::B2DPolyPolygon
;
57 VCartesianAxis::VCartesianAxis( const AxisProperties
& rAxisProperties
58 , const Reference
< util::XNumberFormatsSupplier
>& xNumberFormatsSupplier
59 , sal_Int32 nDimensionIndex
, sal_Int32 nDimensionCount
60 , PlottingPositionHelper
* pPosHelper
)//takes ownership
61 : VAxisBase( nDimensionIndex
, nDimensionCount
, rAxisProperties
, xNumberFormatsSupplier
)
64 m_pPosHelper
= pPosHelper
;
66 m_pPosHelper
= new PlottingPositionHelper();
69 VCartesianAxis::~VCartesianAxis()
72 m_pPosHelper
= nullptr;
75 static void lcl_ResizeTextShapeToFitAvailableSpace( Reference
< drawing::XShape
> const & xShape2DText
,
76 const AxisLabelProperties
& rAxisLabelProperties
,
77 const OUString
& rLabel
,
78 const tNameSequence
& rPropNames
,
79 const tAnySequence
& rPropValues
,
80 const bool bIsHorizontalAxis
)
82 uno::Reference
< text::XTextRange
> xTextRange( xShape2DText
, uno::UNO_QUERY
);
84 if( !xTextRange
.is() )
87 const sal_Int32 nFullSize
= bIsHorizontalAxis
? rAxisLabelProperties
.m_aFontReferenceSize
.Height
: rAxisLabelProperties
.m_aFontReferenceSize
.Width
;
89 if( !nFullSize
|| !rLabel
.getLength() )
92 sal_Int32 nMaxLabelsSize
= bIsHorizontalAxis
? rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Height
: rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Width
;
93 const sal_Int32 nAvgCharWidth
= xShape2DText
->getSize().Width
/ rLabel
.getLength();
94 const sal_Int32 nTextSize
= bIsHorizontalAxis
? ShapeFactory::getSizeAfterRotation(xShape2DText
, rAxisLabelProperties
.fRotationAngleDegree
).Height
:
95 ShapeFactory::getSizeAfterRotation(xShape2DText
, rAxisLabelProperties
.fRotationAngleDegree
).Width
;
100 const OUString sDots
= "...";
101 const sal_Int32 nCharsToRemove
= ( nTextSize
- nMaxLabelsSize
) / nAvgCharWidth
+ 1;
102 sal_Int32 nNewLen
= rLabel
.getLength() - nCharsToRemove
- sDots
.getLength();
103 // Prevent from showing only dots
105 nNewLen
= ( rLabel
.getLength() >= sDots
.getLength() ) ? sDots
.getLength() : rLabel
.getLength();
107 bool bCrop
= nCharsToRemove
> 0;
111 OUString aNewLabel
= rLabel
.copy( 0, nNewLen
);
112 if( nNewLen
> sDots
.getLength() )
114 xTextRange
->setString( aNewLabel
);
116 uno::Reference
< beans::XPropertySet
> xProp( xTextRange
, uno::UNO_QUERY
);
119 PropertyMapper::setMultiProperties( rPropNames
, rPropValues
, xProp
);
123 static Reference
< drawing::XShape
> createSingleLabel(
124 const Reference
< lang::XMultiServiceFactory
>& xShapeFactory
125 , const Reference
< drawing::XShapes
>& xTarget
126 , const awt::Point
& rAnchorScreenPosition2D
127 , const OUString
& rLabel
128 , const AxisLabelProperties
& rAxisLabelProperties
129 , const AxisProperties
& rAxisProperties
130 , const tNameSequence
& rPropNames
131 , const tAnySequence
& rPropValues
132 , const bool bIsHorizontalAxis
138 // #i78696# use mathematically correct rotation now
139 const double fRotationAnglePi(-basegfx::deg2rad(rAxisLabelProperties
.fRotationAngleDegree
));
140 uno::Any aATransformation
= ShapeFactory::makeTransformation( rAnchorScreenPosition2D
, fRotationAnglePi
);
141 OUString aLabel
= ShapeFactory::getStackedString( rLabel
, rAxisLabelProperties
.bStackCharacters
);
143 Reference
< drawing::XShape
> xShape2DText
= ShapeFactory::getOrCreateShapeFactory(xShapeFactory
)
144 ->createText( xTarget
, aLabel
, rPropNames
, rPropValues
, aATransformation
);
146 if( rAxisProperties
.m_bLimitSpaceForLabels
)
147 lcl_ResizeTextShapeToFitAvailableSpace(xShape2DText
, rAxisLabelProperties
, aLabel
, rPropNames
, rPropValues
, bIsHorizontalAxis
);
149 LabelPositionHelper::correctPositionForRotation( xShape2DText
150 , rAxisProperties
.maLabelAlignment
.meAlignment
, rAxisLabelProperties
.fRotationAngleDegree
, rAxisProperties
.m_bComplexCategories
);
155 static bool lcl_doesShapeOverlapWithTickmark( const Reference
< drawing::XShape
>& xShape
156 , double fRotationAngleDegree
157 , const basegfx::B2DVector
& rTickScreenPosition
)
162 ::basegfx::B2IRectangle aShapeRect
= BaseGFXHelper::makeRectangle(xShape
->getPosition(), ShapeFactory::getSizeAfterRotation( xShape
, fRotationAngleDegree
));
164 basegfx::B2IVector
aPosition(
165 static_cast<sal_Int32
>( rTickScreenPosition
.getX() )
166 , static_cast<sal_Int32
>( rTickScreenPosition
.getY() ) );
167 return aShapeRect
.isInside(aPosition
);
170 static void lcl_getRotatedPolygon( B2DPolygon
&aPoly
, const ::basegfx::B2DRectangle
&aRect
, const awt::Point
&aPos
, const double fRotationAngleDegree
)
172 aPoly
= basegfx::utils::createPolygonFromRect( aRect
);
174 // For rotating the rectangle we use the opposite angle,
175 // since `B2DHomMatrix` class used for
176 // representing the transformation, performs rotations in the positive
177 // direction (from the X axis to the Y axis). However since the coordinate
178 // system used by the chart has the Y-axis pointing downward, a rotation in
179 // the positive direction means a clockwise rotation. On the contrary text
180 // labels are rotated counterclockwise.
181 // The rotation is performed around the top-left vertex of the rectangle
182 // which is then moved to its final position by using the top-left
183 // vertex of the text label bounding box (aPos) as the translation vector.
184 ::basegfx::B2DHomMatrix aMatrix
;
185 aMatrix
.rotate(-basegfx::deg2rad(fRotationAngleDegree
));
186 aMatrix
.translate( aPos
.X
, aPos
.Y
);
187 aPoly
.transform( aMatrix
);
190 static bool doesOverlap( const Reference
< drawing::XShape
>& xShape1
191 , const Reference
< drawing::XShape
>& xShape2
192 , double fRotationAngleDegree
)
194 if( !xShape1
.is() || !xShape2
.is() )
197 ::basegfx::B2DRectangle
aRect1( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape1
->getSize()));
198 ::basegfx::B2DRectangle
aRect2( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape2
->getSize()));
202 lcl_getRotatedPolygon( aPoly1
, aRect1
, xShape1
->getPosition(), fRotationAngleDegree
);
203 lcl_getRotatedPolygon( aPoly2
, aRect2
, xShape2
->getPosition(), fRotationAngleDegree
);
205 B2DPolyPolygon aPolyPoly1
, aPolyPoly2
;
206 aPolyPoly1
.append( aPoly1
);
207 aPolyPoly2
.append( aPoly2
);
208 B2DPolyPolygon overlapPoly
= ::basegfx::utils::clipPolyPolygonOnPolyPolygon( aPolyPoly1
, aPolyPoly2
, true, false );
210 return (overlapPoly
.count() > 0);
213 static void removeShapesAtWrongRhythm( TickIter
& rIter
214 , sal_Int32 nCorrectRhythm
215 , sal_Int32 nMaxTickToCheck
216 , const Reference
< drawing::XShapes
>& xTarget
)
219 for( TickInfo
* pTickInfo
= rIter
.firstInfo()
220 ; pTickInfo
&& nTick
<= nMaxTickToCheck
221 ; pTickInfo
= rIter
.nextInfo(), nTick
++ )
223 //remove labels which does not fit into the rhythm
224 if( nTick
%nCorrectRhythm
!= 0)
226 if(pTickInfo
->xTextShape
.is())
228 xTarget
->remove(pTickInfo
->xTextShape
);
229 pTickInfo
->xTextShape
= nullptr;
238 * If the labels are staggered and bInnerLine is true we iterate through
239 * only those labels that are closer to the diagram.
241 * If the labels are staggered and bInnerLine is false we iterate through
242 * only those that are farther from the diagram.
244 * If the labels are not staggered we iterate through all labels.
246 class LabelIterator
: public TickIter
249 LabelIterator( TickInfoArrayType
& rTickInfoVector
250 , const AxisLabelStaggering eAxisLabelStaggering
253 virtual TickInfo
* firstInfo() override
;
254 virtual TickInfo
* nextInfo() override
;
257 PureTickIter m_aPureTickIter
;
258 const AxisLabelStaggering m_eAxisLabelStaggering
;
264 LabelIterator::LabelIterator( TickInfoArrayType
& rTickInfoVector
265 , const AxisLabelStaggering eAxisLabelStaggering
267 : m_aPureTickIter( rTickInfoVector
)
268 , m_eAxisLabelStaggering(eAxisLabelStaggering
)
269 , m_bInnerLine(bInnerLine
)
273 TickInfo
* LabelIterator::firstInfo()
275 TickInfo
* pTickInfo
= m_aPureTickIter
.firstInfo();
276 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() )
277 pTickInfo
= m_aPureTickIter
.nextInfo();
280 if( (m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerEven
&& m_bInnerLine
)
282 (m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerOdd
&& !m_bInnerLine
)
287 pTickInfo
= m_aPureTickIter
.nextInfo();
288 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() );
295 TickInfo
* LabelIterator::nextInfo()
297 TickInfo
* pTickInfo
= nullptr;
300 pTickInfo
= m_aPureTickIter
.nextInfo();
301 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() );
303 if( m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerEven
304 || m_eAxisLabelStaggering
==AxisLabelStaggering::StaggerOdd
)
308 pTickInfo
= m_aPureTickIter
.nextInfo();
309 while( pTickInfo
&& !pTickInfo
->xTextShape
.is() );
314 static B2DVector
lcl_getLabelsDistance( TickIter
& rIter
, const B2DVector
& rDistanceTickToText
, double fRotationAngleDegree
)
316 //calculates the height or width of a line of labels
317 //thus a following line of labels can be shifted for that distance
321 sal_Int32 nDistanceTickToText
= static_cast<sal_Int32
>( rDistanceTickToText
.getLength() );
322 if( nDistanceTickToText
==0.0)
325 B2DVector
aStaggerDirection(rDistanceTickToText
);
326 aStaggerDirection
.normalize();
328 sal_Int32 nDistance
=0;
329 Reference
< drawing::XShape
> xShape2DText
;
330 for( TickInfo
* pTickInfo
= rIter
.firstInfo()
332 ; pTickInfo
= rIter
.nextInfo() )
334 xShape2DText
= pTickInfo
->xTextShape
;
335 if( xShape2DText
.is() )
337 awt::Size aSize
= ShapeFactory::getSizeAfterRotation( xShape2DText
, fRotationAngleDegree
);
338 if(fabs(aStaggerDirection
.getX())>fabs(aStaggerDirection
.getY()))
339 nDistance
= std::max(nDistance
,aSize
.Width
);
341 nDistance
= std::max(nDistance
,aSize
.Height
);
345 aRet
= aStaggerDirection
*nDistance
;
347 //add extra distance for vertical distance
348 if(fabs(aStaggerDirection
.getX())>fabs(aStaggerDirection
.getY()))
349 aRet
+= rDistanceTickToText
;
354 static void lcl_shiftLabels( TickIter
& rIter
, const B2DVector
& rStaggerDistance
)
356 if(rStaggerDistance
.getLength()==0.0)
358 Reference
< drawing::XShape
> xShape2DText
;
359 for( TickInfo
* pTickInfo
= rIter
.firstInfo()
361 ; pTickInfo
= rIter
.nextInfo() )
363 xShape2DText
= pTickInfo
->xTextShape
;
364 if( xShape2DText
.is() )
366 awt::Point aPos
= xShape2DText
->getPosition();
367 aPos
.X
+= static_cast<sal_Int32
>(rStaggerDistance
.getX());
368 aPos
.Y
+= static_cast<sal_Int32
>(rStaggerDistance
.getY());
369 xShape2DText
->setPosition( aPos
);
374 static bool lcl_hasWordBreak( const Reference
<drawing::XShape
>& xShape
)
379 SvxShape
* pShape
= comphelper::getUnoTunnelImplementation
<SvxShape
>(xShape
);
380 SvxShapeText
* pShapeText
= dynamic_cast<SvxShapeText
*>(pShape
);
384 SvxTextEditSource
* pTextEditSource
= dynamic_cast<SvxTextEditSource
*>(pShapeText
->GetEditSource());
385 if (!pTextEditSource
)
388 pTextEditSource
->UpdateOutliner();
389 SvxTextForwarder
* pTextForwarder
= pTextEditSource
->GetTextForwarder();
393 sal_Int32 nParaCount
= pTextForwarder
->GetParagraphCount();
394 for ( sal_Int32 nPara
= 0; nPara
< nParaCount
; ++nPara
)
396 sal_Int32 nLineCount
= pTextForwarder
->GetLineCount( nPara
);
397 for ( sal_Int32 nLine
= 0; nLine
< nLineCount
; ++nLine
)
399 sal_Int32 nLineStart
= 0;
400 sal_Int32 nLineEnd
= 0;
401 pTextForwarder
->GetLineBoundaries( nLineStart
, nLineEnd
, nPara
, nLine
);
402 assert(nLineStart
>= 0);
403 sal_Int32 nWordStart
= 0;
404 sal_Int32 nWordEnd
= 0;
405 if ( pTextForwarder
->GetWordIndices( nPara
, nLineStart
, nWordStart
, nWordEnd
) &&
406 ( nWordStart
!= nLineStart
) )
416 static OUString
getTextLabelString(
417 const FixedNumberFormatter
& rFixedNumberFormatter
, const uno::Sequence
<OUString
>* pCategories
,
418 const TickInfo
* pTickInfo
, bool bComplexCat
, Color
& rExtraColor
, bool& rHasExtraColor
)
422 // This is a normal category axis. Get the label string from the
423 // label string array.
424 sal_Int32 nIndex
= static_cast<sal_Int32
>(pTickInfo
->getUnscaledTickValue()) - 1; //first category (index 0) matches with real number 1.0
425 if( nIndex
>=0 && nIndex
<pCategories
->getLength() )
426 return (*pCategories
)[nIndex
];
430 else if (bComplexCat
)
432 // This is a complex category axis. The label is stored in the tick.
433 return pTickInfo
->aText
;
436 // This is a numeric axis. Format the original tick value per number format.
437 return rFixedNumberFormatter
.getFormattedString(pTickInfo
->getUnscaledTickValue(), rExtraColor
, rHasExtraColor
);
440 static void getAxisLabelProperties(
441 tNameSequence
& rPropNames
, tAnySequence
& rPropValues
, const AxisProperties
& rAxisProp
,
442 const AxisLabelProperties
& rAxisLabelProp
,
443 sal_Int32 nLimitedSpaceForText
, bool bLimitedHeight
)
445 Reference
<beans::XPropertySet
> xProps(rAxisProp
.m_xAxisModel
, uno::UNO_QUERY
);
447 PropertyMapper::getTextLabelMultiPropertyLists(
448 xProps
, rPropNames
, rPropValues
, false, nLimitedSpaceForText
, bLimitedHeight
, false);
450 LabelPositionHelper::doDynamicFontResize(
451 rPropValues
, rPropNames
, xProps
, rAxisLabelProp
.m_aFontReferenceSize
);
453 LabelPositionHelper::changeTextAdjustment(
454 rPropValues
, rPropNames
, rAxisProp
.maLabelAlignment
.meAlignment
);
460 * Iterate through only 3 ticks including the one that has the longest text
461 * length. When the first tick has the longest text, it iterates through
462 * the first 3 ticks. Otherwise it iterates through 3 ticks such that the
463 * 2nd tick is the one with the longest text.
465 class MaxLabelTickIter
: public TickIter
468 MaxLabelTickIter( TickInfoArrayType
& rTickInfoVector
, size_t nLongestLabelIndex
);
470 virtual TickInfo
* firstInfo() override
;
471 virtual TickInfo
* nextInfo() override
;
474 TickInfoArrayType
& m_rTickInfoVector
;
475 std::vector
<size_t> m_aValidIndices
;
476 size_t m_nCurrentIndex
;
481 MaxLabelTickIter::MaxLabelTickIter(
482 TickInfoArrayType
& rTickInfoVector
, size_t nLongestLabelIndex
) :
483 m_rTickInfoVector(rTickInfoVector
), m_nCurrentIndex(0)
485 assert(!rTickInfoVector
.empty()); // should be checked by the caller.
486 assert(nLongestLabelIndex
< rTickInfoVector
.size());
488 size_t nMaxIndex
= m_rTickInfoVector
.size()-1;
489 if (nLongestLabelIndex
>= nMaxIndex
-1)
490 nLongestLabelIndex
= 0;
492 if (nLongestLabelIndex
> 0)
493 m_aValidIndices
.push_back(nLongestLabelIndex
-1);
495 m_aValidIndices
.push_back(nLongestLabelIndex
);
497 while (m_aValidIndices
.size() < 3)
499 ++nLongestLabelIndex
;
500 if (nLongestLabelIndex
> nMaxIndex
)
503 m_aValidIndices
.push_back(nLongestLabelIndex
);
507 TickInfo
* MaxLabelTickIter::firstInfo()
510 if (m_nCurrentIndex
< m_aValidIndices
.size())
511 return &m_rTickInfoVector
[m_aValidIndices
[m_nCurrentIndex
]];
515 TickInfo
* MaxLabelTickIter::nextInfo()
518 if (m_nCurrentIndex
< m_aValidIndices
.size())
519 return &m_rTickInfoVector
[m_aValidIndices
[m_nCurrentIndex
]];
523 bool VCartesianAxis::isBreakOfLabelsAllowed(
524 const AxisLabelProperties
& rAxisLabelProperties
, bool bIsHorizontalAxis
, bool bIsVerticalAxis
) const
526 if( m_aTextLabels
.getLength() > 100 )
528 if( !rAxisLabelProperties
.bLineBreakAllowed
)
530 if( rAxisLabelProperties
.bStackCharacters
)
532 //no break for value axis
533 if( !m_bUseTextLabels
)
535 if( !( rAxisLabelProperties
.fRotationAngleDegree
== 0.0 ||
536 rAxisLabelProperties
.fRotationAngleDegree
== 90.0 ||
537 rAxisLabelProperties
.fRotationAngleDegree
== 270.0 ) )
539 //no break for complex vertical category axis
540 if( !m_aAxisProperties
.m_bSwapXAndY
)
541 return bIsHorizontalAxis
;
542 else if( m_aAxisProperties
.m_bSwapXAndY
&& !m_aAxisProperties
.m_bComplexCategories
)
543 return bIsVerticalAxis
;
549 bool canAutoAdjustLabelPlacement(
550 const AxisLabelProperties
& rAxisLabelProperties
, bool bIsHorizontalAxis
, bool bIsVerticalAxis
)
552 // joined prerequisite checks for auto rotate and auto stagger
553 if( rAxisLabelProperties
.bOverlapAllowed
)
555 if( rAxisLabelProperties
.bLineBreakAllowed
) // auto line break may conflict with...
557 if( rAxisLabelProperties
.fRotationAngleDegree
!= 0.0 )
559 // automatic adjusting labels only works for
560 // horizontal axis with horizontal text
561 // or vertical axis with vertical text
562 if( bIsHorizontalAxis
)
563 return !rAxisLabelProperties
.bStackCharacters
;
564 if( bIsVerticalAxis
)
565 return rAxisLabelProperties
.bStackCharacters
;
569 bool isAutoStaggeringOfLabelsAllowed(
570 const AxisLabelProperties
& rAxisLabelProperties
, bool bIsHorizontalAxis
, bool bIsVerticalAxis
)
572 if( rAxisLabelProperties
.eStaggering
!= AxisLabelStaggering::StaggerAuto
)
574 return canAutoAdjustLabelPlacement(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
);
577 // make clear that we check for auto rotation prerequisites
578 const auto& isAutoRotatingOfLabelsAllowed
= canAutoAdjustLabelPlacement
;
581 void VCartesianAxis::createAllTickInfosFromComplexCategories( TickInfoArraysType
& rAllTickInfos
, bool bShiftedPosition
)
583 //no minor tickmarks will be generated!
584 //order is: inner labels first , outer labels last (that is different to all other TickIter cases)
585 if(!bShiftedPosition
)
587 rAllTickInfos
.clear();
589 sal_Int32 nLevelCount
= m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoryLevelCount();
590 for( ; nLevel
<nLevelCount
; nLevel
++ )
592 TickInfoArrayType aTickInfoVector
;
593 const std::vector
<ComplexCategory
>* pComplexCategories
=
594 m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoriesByLevel(nLevel
);
596 if (!pComplexCategories
)
599 sal_Int32 nCatIndex
= 0;
601 for (auto const& complexCategory
: *pComplexCategories
)
603 TickInfo
aTickInfo(nullptr);
604 sal_Int32 nCount
= complexCategory
.Count
;
605 if( nCatIndex
+ 1.0 + nCount
>= m_aScale
.Maximum
)
607 nCount
= static_cast<sal_Int32
>(m_aScale
.Maximum
- 1.0 - nCatIndex
);
611 aTickInfo
.fScaledTickValue
= nCatIndex
+ 1.0 + nCount
/2.0;
612 aTickInfo
.nFactorForLimitedTextWidth
= nCount
;
613 aTickInfo
.aText
= complexCategory
.Text
;
614 aTickInfoVector
.push_back(aTickInfo
);
616 if( nCatIndex
+ 1.0 >= m_aScale
.Maximum
)
619 rAllTickInfos
.push_back(aTickInfoVector
);
622 else //bShiftedPosition==false
624 rAllTickInfos
.clear();
626 sal_Int32 nLevelCount
= m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoryLevelCount();
627 for( ; nLevel
<nLevelCount
; nLevel
++ )
629 TickInfoArrayType aTickInfoVector
;
630 const std::vector
<ComplexCategory
>* pComplexCategories
=
631 m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoriesByLevel(nLevel
);
632 sal_Int32 nCatIndex
= 0;
633 if (pComplexCategories
)
635 for (auto const& complexCategory
: *pComplexCategories
)
637 TickInfo
aTickInfo(nullptr);
638 aTickInfo
.fScaledTickValue
= nCatIndex
+ 1.0;
639 aTickInfoVector
.push_back(aTickInfo
);
640 nCatIndex
+= complexCategory
.Count
;
641 if( nCatIndex
+ 1.0 > m_aScale
.Maximum
)
646 //fill up with single ticks until maximum scale
647 while( nCatIndex
+ 1.0 < m_aScale
.Maximum
)
649 TickInfo
aTickInfo(nullptr);
650 aTickInfo
.fScaledTickValue
= nCatIndex
+ 1.0;
651 aTickInfoVector
.push_back(aTickInfo
);
656 //add an additional tick at the end
658 TickInfo
aTickInfo(nullptr);
659 aTickInfo
.fScaledTickValue
= m_aScale
.Maximum
;
660 aTickInfoVector
.push_back(aTickInfo
);
662 rAllTickInfos
.push_back(aTickInfoVector
);
667 void VCartesianAxis::createAllTickInfos( TickInfoArraysType
& rAllTickInfos
)
669 if( isComplexCategoryAxis() )
670 createAllTickInfosFromComplexCategories( rAllTickInfos
, false );
672 VAxisBase::createAllTickInfos(rAllTickInfos
);
675 TickIter
* VCartesianAxis::createLabelTickIterator( sal_Int32 nTextLevel
)
677 if( nTextLevel
>=0 && nTextLevel
< static_cast< sal_Int32
>(m_aAllTickInfos
.size()) )
678 return new PureTickIter( m_aAllTickInfos
[nTextLevel
] );
682 TickIter
* VCartesianAxis::createMaximumLabelTickIterator( sal_Int32 nTextLevel
)
684 if( isComplexCategoryAxis() || isDateAxis() )
686 return createLabelTickIterator( nTextLevel
); //mmmm maybe todo: create less than all texts here
692 if( !m_aAllTickInfos
.empty() )
694 size_t nLongestLabelIndex
= m_bUseTextLabels
? getIndexOfLongestLabel(m_aTextLabels
) : 0;
695 if (nLongestLabelIndex
>= m_aAllTickInfos
[0].size())
698 return new MaxLabelTickIter( m_aAllTickInfos
[0], nLongestLabelIndex
);
705 sal_Int32
VCartesianAxis::getTextLevelCount() const
707 sal_Int32 nTextLevelCount
= 1;
708 if( isComplexCategoryAxis() )
709 nTextLevelCount
= m_aAxisProperties
.m_pExplicitCategoriesProvider
->getCategoryLevelCount();
710 return nTextLevelCount
;
713 bool VCartesianAxis::createTextShapes(
714 const Reference
<drawing::XShapes
>& xTarget
, TickIter
& rTickIter
,
715 AxisLabelProperties
& rAxisLabelProperties
, TickFactory2D
const * pTickFactory
,
716 sal_Int32 nScreenDistanceBetweenTicks
)
718 const bool bIsHorizontalAxis
= pTickFactory
->isHorizontalAxis();
719 const bool bIsVerticalAxis
= pTickFactory
->isVerticalAxis();
721 if( m_bUseTextLabels
&& (m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_NEAR_AXIS
||
722 m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_START
))
724 if (bIsHorizontalAxis
)
726 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Y
= pTickFactory
->getXaxisStartPos().getY();
727 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Height
= rAxisLabelProperties
.m_aFontReferenceSize
.Height
- rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Y
;
729 else if (bIsVerticalAxis
)
731 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.X
= 0;
732 rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Width
= pTickFactory
->getXaxisStartPos().getX();
736 if (!isBreakOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) &&
737 !isAutoStaggeringOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) &&
738 !rAxisLabelProperties
.isStaggered())
739 return createTextShapesSimple(xTarget
, rTickIter
, rAxisLabelProperties
, pTickFactory
);
741 FixedNumberFormatter
aFixedNumberFormatter(
742 m_xNumberFormatsSupplier
, rAxisLabelProperties
.nNumberFormatKey
);
744 bool bIsStaggered
= rAxisLabelProperties
.isStaggered();
745 B2DVector aTextToTickDistance
= pTickFactory
->getDistanceAxisTickToText(m_aAxisProperties
, true);
746 sal_Int32 nLimitedSpaceForText
= -1;
748 if( isBreakOfLabelsAllowed( rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
750 nLimitedSpaceForText
= nScreenDistanceBetweenTicks
;
752 nLimitedSpaceForText
*= 2;
754 if( nLimitedSpaceForText
> 0 )
755 { //reduce space for a small amount to have a visible distance between the labels:
756 sal_Int32 nReduce
= (nLimitedSpaceForText
*5)/100;
759 nLimitedSpaceForText
-= nReduce
;
762 // recalculate the nLimitedSpaceForText in case of 90 and 270 degree if the text break is true
763 if ( rAxisLabelProperties
.fRotationAngleDegree
== 90.0 || rAxisLabelProperties
.fRotationAngleDegree
== 270.0 )
765 nLimitedSpaceForText
= rAxisLabelProperties
.m_aMaximumSpaceForLabels
.Height
;
766 m_aAxisProperties
.m_bLimitSpaceForLabels
= false;
769 // recalculate the nLimitedSpaceForText in case of vertical category axis if the text break is true
770 if ( m_aAxisProperties
.m_bSwapXAndY
&& bIsVerticalAxis
&& rAxisLabelProperties
.fRotationAngleDegree
== 0.0 )
772 nLimitedSpaceForText
= pTickFactory
->getXaxisStartPos().getX();
773 m_aAxisProperties
.m_bLimitSpaceForLabels
= false;
777 // Stores an array of text label strings in case of a normal
778 // (non-complex) category axis.
779 const uno::Sequence
<OUString
>* pCategories
= nullptr;
780 if( m_bUseTextLabels
&& !m_aAxisProperties
.m_bComplexCategories
)
781 pCategories
= &m_aTextLabels
;
784 if( !m_aAxisProperties
.m_bSwapXAndY
)
785 bLimitedHeight
= fabs(aTextToTickDistance
.getX()) > fabs(aTextToTickDistance
.getY());
787 bLimitedHeight
= fabs(aTextToTickDistance
.getX()) < fabs(aTextToTickDistance
.getY());
788 //prepare properties for multipropertyset-interface of shape
789 tNameSequence aPropNames
;
790 tAnySequence aPropValues
;
791 getAxisLabelProperties(aPropNames
, aPropValues
, m_aAxisProperties
, rAxisLabelProperties
, nLimitedSpaceForText
, bLimitedHeight
);
793 uno::Any
* pColorAny
= PropertyMapper::getValuePointer(aPropValues
,aPropNames
,"CharColor");
794 Color nColor
= COL_AUTO
;
796 *pColorAny
>>= nColor
;
798 uno::Any
* pLimitedSpaceAny
= PropertyMapper::getValuePointerForLimitedSpace(aPropValues
,aPropNames
,bLimitedHeight
);
800 const TickInfo
* pPreviousVisibleTickInfo
= nullptr;
801 const TickInfo
* pPREPreviousVisibleTickInfo
= nullptr;
803 for( TickInfo
* pTickInfo
= rTickIter
.firstInfo()
805 ; pTickInfo
= rTickIter
.nextInfo(), nTick
++ )
807 const TickInfo
* pLastVisibleNeighbourTickInfo
= bIsStaggered
?
808 pPREPreviousVisibleTickInfo
: pPreviousVisibleTickInfo
;
810 //don't create labels which does not fit into the rhythm
811 if( nTick
%rAxisLabelProperties
.nRhythm
!= 0 )
814 //don't create labels for invisible ticks
815 if( !pTickInfo
->bPaintIt
)
818 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.bOverlapAllowed
)
820 // Overlapping is not allowed. If the label overlaps with its
821 // neighboring label, try increasing the tick interval (or rhythm
822 // as it's called) and start over.
824 if( lcl_doesShapeOverlapWithTickmark( pLastVisibleNeighbourTickInfo
->xTextShape
825 , rAxisLabelProperties
.fRotationAngleDegree
826 , pTickInfo
->aTickScreenPosition
) )
828 // This tick overlaps with its neighbor. Try to stagger (if
829 // auto staggering is allowed) to avoid overlapping.
831 bool bOverlapsAfterAutoStagger
= true;
832 if( !bIsStaggered
&& isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
835 rAxisLabelProperties
.eStaggering
= AxisLabelStaggering::StaggerEven
;
836 pLastVisibleNeighbourTickInfo
= pPREPreviousVisibleTickInfo
;
837 if( !pLastVisibleNeighbourTickInfo
||
838 !lcl_doesShapeOverlapWithTickmark( pLastVisibleNeighbourTickInfo
->xTextShape
839 , rAxisLabelProperties
.fRotationAngleDegree
840 , pTickInfo
->aTickScreenPosition
) )
841 bOverlapsAfterAutoStagger
= false;
844 if (bOverlapsAfterAutoStagger
)
846 // Still overlaps with its neighbor even after staggering.
847 // Increment the visible tick intervals (if that's
848 // allowed) and start over.
850 rAxisLabelProperties
.nRhythm
++;
851 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.nRhythm
, nTick
, xTarget
);
857 bool bHasExtraColor
=false;
860 OUString aLabel
= getTextLabelString(
861 aFixedNumberFormatter
, pCategories
, pTickInfo
, isComplexCategoryAxis(),
862 nExtraColor
, bHasExtraColor
);
865 *pColorAny
<<= bHasExtraColor
?nExtraColor
:nColor
;
867 *pLimitedSpaceAny
<<= sal_Int32(nLimitedSpaceForText
*pTickInfo
->nFactorForLimitedTextWidth
);
869 B2DVector aTickScreenPos2D
= pTickInfo
->aTickScreenPosition
;
870 aTickScreenPos2D
+= aTextToTickDistance
;
871 awt::Point
aAnchorScreenPosition2D(
872 static_cast<sal_Int32
>(aTickScreenPos2D
.getX())
873 ,static_cast<sal_Int32
>(aTickScreenPos2D
.getY()));
875 //create single label
876 if(!pTickInfo
->xTextShape
.is())
877 pTickInfo
->xTextShape
= createSingleLabel( m_xShapeFactory
, xTarget
878 , aAnchorScreenPosition2D
, aLabel
879 , rAxisLabelProperties
, m_aAxisProperties
880 , aPropNames
, aPropValues
, bIsHorizontalAxis
);
881 if(!pTickInfo
->xTextShape
.is())
884 recordMaximumTextSize( pTickInfo
->xTextShape
, rAxisLabelProperties
.fRotationAngleDegree
);
886 // Label has multiple lines and the words are broken
887 if( nLimitedSpaceForText
>0 && !rAxisLabelProperties
.bOverlapAllowed
888 && rAxisLabelProperties
.fRotationAngleDegree
== 0.0
889 && lcl_hasWordBreak( pTickInfo
->xTextShape
) )
891 // Label has multiple lines and belongs to a complex category
892 // axis. Rotate 90 degrees to try to avoid overlaps.
893 if ( m_aAxisProperties
.m_bComplexCategories
)
895 rAxisLabelProperties
.fRotationAngleDegree
= 90;
897 rAxisLabelProperties
.bLineBreakAllowed
= false;
898 m_aAxisLabelProperties
.fRotationAngleDegree
= rAxisLabelProperties
.fRotationAngleDegree
;
899 removeTextShapesFromTicks();
903 //if NO OVERLAP -> remove overlapping shapes
904 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.bOverlapAllowed
)
906 // Check if the label still overlaps with its neighbor.
907 if( doesOverlap( pLastVisibleNeighbourTickInfo
->xTextShape
, pTickInfo
->xTextShape
, rAxisLabelProperties
.fRotationAngleDegree
) )
909 // It overlaps. Check if staggering helps.
910 bool bOverlapsAfterAutoStagger
= true;
911 if( !bIsStaggered
&& isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
913 // Compatibility option: starting from LibreOffice 5.1 the rotated
914 // layout is preferred to staggering for axis labels.
915 if( !isAutoRotatingOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
)
916 || m_aAxisProperties
.m_bTryStaggeringFirst
)
919 rAxisLabelProperties
.eStaggering
= AxisLabelStaggering::StaggerEven
;
920 pLastVisibleNeighbourTickInfo
= pPREPreviousVisibleTickInfo
;
921 if( !pLastVisibleNeighbourTickInfo
||
922 !lcl_doesShapeOverlapWithTickmark( pLastVisibleNeighbourTickInfo
->xTextShape
923 , rAxisLabelProperties
.fRotationAngleDegree
924 , pTickInfo
->aTickScreenPosition
) )
925 bOverlapsAfterAutoStagger
= false;
929 if (bOverlapsAfterAutoStagger
)
931 // Staggering didn't solve the overlap.
932 if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
934 // Try auto-rotating the labels at 45 degrees and
935 // start over. This rotation angle will be stored for
936 // all future text shape creation runs.
937 // The nRhythm parameter is reset to 1 since the layout
938 // used for text labels is changed.
939 rAxisLabelProperties
.autoRotate45();
940 m_aAxisLabelProperties
.fRotationAngleDegree
= rAxisLabelProperties
.fRotationAngleDegree
; // Store it for future runs.
941 removeTextShapesFromTicks();
942 rAxisLabelProperties
.nRhythm
= 1;
946 // Try incrementing the tick interval and start over.
947 rAxisLabelProperties
.nRhythm
++;
948 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.nRhythm
, nTick
, xTarget
);
954 pPREPreviousVisibleTickInfo
= pPreviousVisibleTickInfo
;
955 pPreviousVisibleTickInfo
= pTickInfo
;
960 bool VCartesianAxis::createTextShapesSimple(
961 const Reference
<drawing::XShapes
>& xTarget
, TickIter
& rTickIter
,
962 AxisLabelProperties
& rAxisLabelProperties
, TickFactory2D
const * pTickFactory
)
964 FixedNumberFormatter
aFixedNumberFormatter(
965 m_xNumberFormatsSupplier
, rAxisLabelProperties
.nNumberFormatKey
);
967 const bool bIsHorizontalAxis
= pTickFactory
->isHorizontalAxis();
968 const bool bIsVerticalAxis
= pTickFactory
->isVerticalAxis();
969 B2DVector aTextToTickDistance
= pTickFactory
->getDistanceAxisTickToText(m_aAxisProperties
, true);
971 // Stores an array of text label strings in case of a normal
972 // (non-complex) category axis.
973 const uno::Sequence
<OUString
>* pCategories
= nullptr;
974 if( m_bUseTextLabels
&& !m_aAxisProperties
.m_bComplexCategories
)
975 pCategories
= &m_aTextLabels
;
977 bool bLimitedHeight
= fabs(aTextToTickDistance
.getX()) > fabs(aTextToTickDistance
.getY());
979 //prepare properties for multipropertyset-interface of shape
980 tNameSequence aPropNames
;
981 tAnySequence aPropValues
;
982 getAxisLabelProperties(aPropNames
, aPropValues
, m_aAxisProperties
, rAxisLabelProperties
, -1, bLimitedHeight
);
984 uno::Any
* pColorAny
= PropertyMapper::getValuePointer(aPropValues
,aPropNames
,"CharColor");
985 Color nColor
= COL_AUTO
;
987 *pColorAny
>>= nColor
;
989 uno::Any
* pLimitedSpaceAny
= PropertyMapper::getValuePointerForLimitedSpace(aPropValues
,aPropNames
,bLimitedHeight
);
991 const TickInfo
* pPreviousVisibleTickInfo
= nullptr;
993 for( TickInfo
* pTickInfo
= rTickIter
.firstInfo()
995 ; pTickInfo
= rTickIter
.nextInfo(), nTick
++ )
997 const TickInfo
* pLastVisibleNeighbourTickInfo
= pPreviousVisibleTickInfo
;
999 //don't create labels which does not fit into the rhythm
1000 if( nTick
%rAxisLabelProperties
.nRhythm
!= 0 )
1003 //don't create labels for invisible ticks
1004 if( !pTickInfo
->bPaintIt
)
1007 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.bOverlapAllowed
)
1009 // Overlapping is not allowed. If the label overlaps with its
1010 // neighboring label, try increasing the tick interval (or rhythm
1011 // as it's called) and start over.
1013 if( lcl_doesShapeOverlapWithTickmark( pLastVisibleNeighbourTickInfo
->xTextShape
1014 , rAxisLabelProperties
.fRotationAngleDegree
1015 , pTickInfo
->aTickScreenPosition
) )
1017 // This tick overlaps with its neighbor. Increment the visible
1018 // tick intervals (if that's allowed) and start over.
1020 rAxisLabelProperties
.nRhythm
++;
1021 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.nRhythm
, nTick
, xTarget
);
1026 bool bHasExtraColor
=false;
1029 OUString aLabel
= getTextLabelString(
1030 aFixedNumberFormatter
, pCategories
, pTickInfo
, isComplexCategoryAxis(),
1031 nExtraColor
, bHasExtraColor
);
1034 *pColorAny
<<= bHasExtraColor
?nExtraColor
:nColor
;
1035 if(pLimitedSpaceAny
)
1036 *pLimitedSpaceAny
<<= sal_Int32(-1*pTickInfo
->nFactorForLimitedTextWidth
);
1038 B2DVector aTickScreenPos2D
= pTickInfo
->aTickScreenPosition
;
1039 aTickScreenPos2D
+= aTextToTickDistance
;
1040 awt::Point
aAnchorScreenPosition2D(
1041 static_cast<sal_Int32
>(aTickScreenPos2D
.getX())
1042 ,static_cast<sal_Int32
>(aTickScreenPos2D
.getY()));
1044 //create single label
1045 if(!pTickInfo
->xTextShape
.is())
1046 pTickInfo
->xTextShape
= createSingleLabel( m_xShapeFactory
, xTarget
1047 , aAnchorScreenPosition2D
, aLabel
1048 , rAxisLabelProperties
, m_aAxisProperties
1049 , aPropNames
, aPropValues
, bIsHorizontalAxis
);
1050 if(!pTickInfo
->xTextShape
.is())
1053 recordMaximumTextSize( pTickInfo
->xTextShape
, rAxisLabelProperties
.fRotationAngleDegree
);
1055 //if NO OVERLAP -> remove overlapping shapes
1056 if( pLastVisibleNeighbourTickInfo
&& !rAxisLabelProperties
.bOverlapAllowed
)
1058 // Check if the label still overlaps with its neighbor.
1059 if( doesOverlap( pLastVisibleNeighbourTickInfo
->xTextShape
, pTickInfo
->xTextShape
, rAxisLabelProperties
.fRotationAngleDegree
) )
1062 if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties
, bIsHorizontalAxis
, bIsVerticalAxis
) )
1064 // Try auto-rotating the labels at 45 degrees and
1065 // start over. This rotation angle will be stored for
1066 // all future text shape creation runs.
1067 // The nRhythm parameter is reset to 1 since the layout
1068 // used for text labels is changed.
1069 rAxisLabelProperties
.autoRotate45();
1070 m_aAxisLabelProperties
.fRotationAngleDegree
= rAxisLabelProperties
.fRotationAngleDegree
; // Store it for future runs.
1071 removeTextShapesFromTicks();
1072 rAxisLabelProperties
.nRhythm
= 1;
1076 // Try incrementing the tick interval and start over.
1077 rAxisLabelProperties
.nRhythm
++;
1078 removeShapesAtWrongRhythm( rTickIter
, rAxisLabelProperties
.nRhythm
, nTick
, xTarget
);
1083 pPreviousVisibleTickInfo
= pTickInfo
;
1088 double VCartesianAxis::getAxisIntersectionValue() const
1090 if (m_aAxisProperties
.m_pfMainLinePositionAtOtherAxis
)
1091 return *m_aAxisProperties
.m_pfMainLinePositionAtOtherAxis
;
1093 double fMin
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMinY();
1094 double fMax
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMaxY();
1096 return (m_aAxisProperties
.m_eCrossoverType
== css::chart::ChartAxisPosition_END
) ? fMax
: fMin
;
1099 double VCartesianAxis::getLabelLineIntersectionValue() const
1101 if (m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_START
)
1102 return (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMinY();
1104 if (m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_END
)
1105 return (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMaxY();
1107 return getAxisIntersectionValue();
1110 double VCartesianAxis::getExtraLineIntersectionValue() const
1113 rtl::math::setNan(&fNan
);
1115 if( !m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
)
1118 double fMin
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMinY();
1119 double fMax
= (m_nDimensionIndex
==1) ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMaxY();
1121 if( *m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
<= fMin
1122 || *m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
>= fMax
)
1125 return *m_aAxisProperties
.m_pfExrtaLinePositionAtOtherAxis
;
1128 B2DVector
VCartesianAxis::getScreenPosition( double fLogicX
, double fLogicY
, double fLogicZ
) const
1130 B2DVector
aRet(0,0);
1134 drawing::Position3D aScenePos
= m_pPosHelper
->transformLogicToScene( fLogicX
, fLogicY
, fLogicZ
, true );
1137 if (m_xLogicTarget
.is() && m_pShapeFactory
)
1139 tPropertyNameMap aDummyPropertyNameMap
;
1140 Reference
< drawing::XShape
> xShape3DAnchor
= m_pShapeFactory
->createCube( m_xLogicTarget
1141 , aScenePos
,drawing::Direction3D(1,1,1), 0, nullptr, aDummyPropertyNameMap
);
1142 awt::Point a2DPos
= xShape3DAnchor
->getPosition(); //get 2D position from xShape3DAnchor
1143 m_xLogicTarget
->remove(xShape3DAnchor
);
1144 aRet
.setX( a2DPos
.X
);
1145 aRet
.setY( a2DPos
.Y
);
1149 OSL_FAIL("cannot calculate screen position in VCartesianAxis::getScreenPosition");
1154 aRet
.setX( aScenePos
.PositionX
);
1155 aRet
.setY( aScenePos
.PositionY
);
1162 VCartesianAxis::ScreenPosAndLogicPos
VCartesianAxis::getScreenPosAndLogicPos( double fLogicX_
, double fLogicY_
, double fLogicZ_
) const
1164 ScreenPosAndLogicPos aRet
;
1165 aRet
.fLogicX
= fLogicX_
;
1166 aRet
.fLogicY
= fLogicY_
;
1167 aRet
.fLogicZ
= fLogicZ_
;
1168 aRet
.aScreenPos
= getScreenPosition( fLogicX_
, fLogicY_
, fLogicZ_
);
1172 typedef std::vector
< VCartesianAxis::ScreenPosAndLogicPos
> tScreenPosAndLogicPosList
;
1178 bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos
& rPos1
, const VCartesianAxis::ScreenPosAndLogicPos
& rPos2
)
1180 return ( rPos1
.aScreenPos
.getX() < rPos2
.aScreenPos
.getX() );
1184 struct lcl_GreaterYPos
1186 bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos
& rPos1
, const VCartesianAxis::ScreenPosAndLogicPos
& rPos2
)
1188 return ( rPos1
.aScreenPos
.getY() > rPos2
.aScreenPos
.getY() );
1194 void VCartesianAxis::get2DAxisMainLine(
1195 B2DVector
& rStart
, B2DVector
& rEnd
, AxisLabelAlignment
& rAlignment
, double fCrossesOtherAxis
) const
1197 //m_aAxisProperties might get updated and changed here because
1198 // the label alignment and inner direction sign depends exactly of the choice of the axis line position which is made here in this method
1200 double const fMinX
= m_pPosHelper
->getLogicMinX();
1201 double const fMinY
= m_pPosHelper
->getLogicMinY();
1202 double const fMinZ
= m_pPosHelper
->getLogicMinZ();
1203 double const fMaxX
= m_pPosHelper
->getLogicMaxX();
1204 double const fMaxY
= m_pPosHelper
->getLogicMaxY();
1205 double const fMaxZ
= m_pPosHelper
->getLogicMaxZ();
1207 double fXOnXPlane
= fMinX
;
1208 double fXOther
= fMaxX
;
1209 int nDifferentValue
= !m_pPosHelper
->isMathematicalOrientationX() ? -1 : 1;
1210 if( !m_pPosHelper
->isSwapXAndY() )
1211 nDifferentValue
*= (m_eLeftWallPos
!= CuboidPlanePosition_Left
) ? -1 : 1;
1213 nDifferentValue
*= (m_eBottomPos
!= CuboidPlanePosition_Bottom
) ? -1 : 1;
1214 if( nDifferentValue
<0 )
1220 double fYOnYPlane
= fMinY
;
1221 double fYOther
= fMaxY
;
1222 nDifferentValue
= !m_pPosHelper
->isMathematicalOrientationY() ? -1 : 1;
1223 if( !m_pPosHelper
->isSwapXAndY() )
1224 nDifferentValue
*= (m_eBottomPos
!= CuboidPlanePosition_Bottom
) ? -1 : 1;
1226 nDifferentValue
*= (m_eLeftWallPos
!= CuboidPlanePosition_Left
) ? -1 : 1;
1227 if( nDifferentValue
<0 )
1233 double fZOnZPlane
= fMaxZ
;
1234 double fZOther
= fMinZ
;
1235 nDifferentValue
= !m_pPosHelper
->isMathematicalOrientationZ() ? -1 : 1;
1236 nDifferentValue
*= (m_eBackWallPos
!= CuboidPlanePosition_Back
) ? -1 : 1;
1237 if( nDifferentValue
<0 )
1243 double fXStart
= fMinX
;
1244 double fYStart
= fMinY
;
1245 double fZStart
= fMinZ
;
1248 double fZEnd
= fZStart
;
1250 if( m_nDimensionIndex
==0 ) //x-axis
1252 if( fCrossesOtherAxis
< fMinY
)
1253 fCrossesOtherAxis
= fMinY
;
1254 else if( fCrossesOtherAxis
> fMaxY
)
1255 fCrossesOtherAxis
= fMaxY
;
1257 fYStart
= fYEnd
= fCrossesOtherAxis
;
1258 fXEnd
=m_pPosHelper
->getLogicMaxX();
1262 if( AxisHelper::isAxisPositioningEnabled() )
1264 if( ::rtl::math::approxEqual( fYOther
, fYStart
) )
1265 fZStart
= fZEnd
= fZOnZPlane
;
1267 fZStart
= fZEnd
= fZOther
;
1271 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1272 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1274 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1275 double fDeltaY
= rEnd
.getY() - rStart
.getY();
1277 //only those points are candidates which are lying on exactly one wall as these are outer edges
1278 tScreenPosAndLogicPosList aPosList
;
1279 aPosList
.push_back( getScreenPosAndLogicPos( fMinX
, fYOnYPlane
, fZOther
) );
1280 aPosList
.push_back( getScreenPosAndLogicPos( fMinX
, fYOther
, fZOnZPlane
) );
1282 if( fabs(fDeltaY
) > fabs(fDeltaX
) )
1284 rAlignment
.meAlignment
= LABEL_ALIGN_LEFT
;
1285 //choose most left positions
1286 std::sort( aPosList
.begin(), aPosList
.end(), lcl_LessXPos() );
1287 rAlignment
.mfLabelDirection
= (fDeltaY
< 0) ? -1.0 : 1.0;
1291 rAlignment
.meAlignment
= LABEL_ALIGN_BOTTOM
;
1292 //choose most bottom positions
1293 std::sort( aPosList
.begin(), aPosList
.end(), lcl_GreaterYPos() );
1294 rAlignment
.mfLabelDirection
= (fDeltaX
< 0) ? -1.0 : 1.0;
1296 ScreenPosAndLogicPos
aBestPos( aPosList
[0] );
1297 fYStart
= fYEnd
= aBestPos
.fLogicY
;
1298 fZStart
= fZEnd
= aBestPos
.fLogicZ
;
1299 if( !m_pPosHelper
->isMathematicalOrientationX() )
1300 rAlignment
.mfLabelDirection
*= -1.0;
1304 else if( m_nDimensionIndex
==1 ) //y-axis
1306 if( fCrossesOtherAxis
< fMinX
)
1307 fCrossesOtherAxis
= fMinX
;
1308 else if( fCrossesOtherAxis
> fMaxX
)
1309 fCrossesOtherAxis
= fMaxX
;
1311 fXStart
= fXEnd
= fCrossesOtherAxis
;
1312 fYEnd
=m_pPosHelper
->getLogicMaxY();
1316 if( AxisHelper::isAxisPositioningEnabled() )
1318 if( ::rtl::math::approxEqual( fXOther
, fXStart
) )
1319 fZStart
= fZEnd
= fZOnZPlane
;
1321 fZStart
= fZEnd
= fZOther
;
1325 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1326 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1328 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1329 double fDeltaY
= rEnd
.getY() - rStart
.getY();
1331 //only those points are candidates which are lying on exactly one wall as these are outer edges
1332 tScreenPosAndLogicPosList aPosList
;
1333 aPosList
.push_back( getScreenPosAndLogicPos( fXOnXPlane
, fMinY
, fZOther
) );
1334 aPosList
.push_back( getScreenPosAndLogicPos( fXOther
, fMinY
, fZOnZPlane
) );
1336 if( fabs(fDeltaY
) > fabs(fDeltaX
) )
1338 rAlignment
.meAlignment
= LABEL_ALIGN_LEFT
;
1339 //choose most left positions
1340 std::sort( aPosList
.begin(), aPosList
.end(), lcl_LessXPos() );
1341 rAlignment
.mfLabelDirection
= (fDeltaY
< 0) ? -1.0 : 1.0;
1345 rAlignment
.meAlignment
= LABEL_ALIGN_BOTTOM
;
1346 //choose most bottom positions
1347 std::sort( aPosList
.begin(), aPosList
.end(), lcl_GreaterYPos() );
1348 rAlignment
.mfLabelDirection
= (fDeltaX
< 0) ? -1.0 : 1.0;
1350 ScreenPosAndLogicPos
aBestPos( aPosList
[0] );
1351 fXStart
= fXEnd
= aBestPos
.fLogicX
;
1352 fZStart
= fZEnd
= aBestPos
.fLogicZ
;
1353 if( !m_pPosHelper
->isMathematicalOrientationY() )
1354 rAlignment
.mfLabelDirection
*= -1.0;
1360 fZEnd
= m_pPosHelper
->getLogicMaxZ();
1361 if( AxisHelper::isAxisPositioningEnabled() )
1363 if( !m_aAxisProperties
.m_bSwapXAndY
)
1365 if( fCrossesOtherAxis
< fMinY
)
1366 fCrossesOtherAxis
= fMinY
;
1367 else if( fCrossesOtherAxis
> fMaxY
)
1368 fCrossesOtherAxis
= fMaxY
;
1369 fYStart
= fYEnd
= fCrossesOtherAxis
;
1371 if( ::rtl::math::approxEqual( fYOther
, fYStart
) )
1372 fXStart
= fXEnd
= fXOnXPlane
;
1374 fXStart
= fXEnd
= fXOther
;
1378 if( fCrossesOtherAxis
< fMinX
)
1379 fCrossesOtherAxis
= fMinX
;
1380 else if( fCrossesOtherAxis
> fMaxX
)
1381 fCrossesOtherAxis
= fMaxX
;
1382 fXStart
= fXEnd
= fCrossesOtherAxis
;
1384 if( ::rtl::math::approxEqual( fXOther
, fXStart
) )
1385 fYStart
= fYEnd
= fYOnYPlane
;
1387 fYStart
= fYEnd
= fYOther
;
1392 if( !m_pPosHelper
->isSwapXAndY() )
1394 fXStart
= fXEnd
= m_pPosHelper
->isMathematicalOrientationX() ? m_pPosHelper
->getLogicMaxX() : m_pPosHelper
->getLogicMinX();
1395 fYStart
= fYEnd
= m_pPosHelper
->isMathematicalOrientationY() ? m_pPosHelper
->getLogicMinY() : m_pPosHelper
->getLogicMaxY();
1399 fXStart
= fXEnd
= m_pPosHelper
->isMathematicalOrientationX() ? m_pPosHelper
->getLogicMinX() : m_pPosHelper
->getLogicMaxX();
1400 fYStart
= fYEnd
= m_pPosHelper
->isMathematicalOrientationY() ? m_pPosHelper
->getLogicMaxY() : m_pPosHelper
->getLogicMinY();
1405 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1406 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1408 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1410 //only those points are candidates which are lying on exactly one wall as these are outer edges
1411 tScreenPosAndLogicPosList aPosList
;
1412 aPosList
.push_back( getScreenPosAndLogicPos( fXOther
, fYOnYPlane
, fMinZ
) );
1413 aPosList
.push_back( getScreenPosAndLogicPos( fXOnXPlane
, fYOther
, fMinZ
) );
1415 std::sort( aPosList
.begin(), aPosList
.end(), lcl_GreaterYPos() );
1416 ScreenPosAndLogicPos
aBestPos( aPosList
[0] );
1417 ScreenPosAndLogicPos
aNotSoGoodPos( aPosList
[1] );
1419 //choose most bottom positions
1420 if( fDeltaX
!= 0.0 ) // prefer left-right alignments
1422 if( aBestPos
.aScreenPos
.getX() > aNotSoGoodPos
.aScreenPos
.getX() )
1423 rAlignment
.meAlignment
= LABEL_ALIGN_RIGHT
;
1425 rAlignment
.meAlignment
= LABEL_ALIGN_LEFT
;
1429 if( aBestPos
.aScreenPos
.getY() > aNotSoGoodPos
.aScreenPos
.getY() )
1430 rAlignment
.meAlignment
= LABEL_ALIGN_BOTTOM
;
1432 rAlignment
.meAlignment
= LABEL_ALIGN_TOP
;
1435 rAlignment
.mfLabelDirection
= (fDeltaX
< 0) ? -1.0 : 1.0;
1436 if( !m_pPosHelper
->isMathematicalOrientationZ() )
1437 rAlignment
.mfLabelDirection
*= -1.0;
1439 fXStart
= fXEnd
= aBestPos
.fLogicX
;
1440 fYStart
= fYEnd
= aBestPos
.fLogicY
;
1445 rStart
= getScreenPosition( fXStart
, fYStart
, fZStart
);
1446 rEnd
= getScreenPosition( fXEnd
, fYEnd
, fZEnd
);
1448 if(m_nDimension
==3 && !AxisHelper::isAxisPositioningEnabled() )
1449 rAlignment
.mfInnerTickDirection
= rAlignment
.mfLabelDirection
;//to behave like before
1451 if(!(m_nDimension
==3 && AxisHelper::isAxisPositioningEnabled()) )
1454 double fDeltaX
= rEnd
.getX() - rStart
.getX();
1455 double fDeltaY
= rEnd
.getY() - rStart
.getY();
1457 if( m_nDimensionIndex
==2 )
1459 if( m_eLeftWallPos
!= CuboidPlanePosition_Left
)
1461 rAlignment
.mfLabelDirection
*= -1.0;
1462 rAlignment
.mfInnerTickDirection
*= -1.0;
1465 rAlignment
.meAlignment
=
1466 (rAlignment
.mfLabelDirection
< 0) ?
1467 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1469 if( ( fDeltaY
<0 && m_aScale
.Orientation
== chart2::AxisOrientation_REVERSE
) ||
1470 ( fDeltaY
>0 && m_aScale
.Orientation
== chart2::AxisOrientation_MATHEMATICAL
) )
1471 rAlignment
.meAlignment
=
1472 (rAlignment
.meAlignment
== LABEL_ALIGN_RIGHT
) ?
1473 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1475 else if( fabs(fDeltaY
) > fabs(fDeltaX
) )
1477 if( m_eBackWallPos
!= CuboidPlanePosition_Back
)
1479 rAlignment
.mfLabelDirection
*= -1.0;
1480 rAlignment
.mfInnerTickDirection
*= -1.0;
1483 rAlignment
.meAlignment
=
1484 (rAlignment
.mfLabelDirection
< 0) ?
1485 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1487 if( ( fDeltaY
<0 && m_aScale
.Orientation
== chart2::AxisOrientation_REVERSE
) ||
1488 ( fDeltaY
>0 && m_aScale
.Orientation
== chart2::AxisOrientation_MATHEMATICAL
) )
1489 rAlignment
.meAlignment
=
1490 (rAlignment
.meAlignment
== LABEL_ALIGN_RIGHT
) ?
1491 LABEL_ALIGN_LEFT
: LABEL_ALIGN_RIGHT
;
1495 if( m_eBackWallPos
!= CuboidPlanePosition_Back
)
1497 rAlignment
.mfLabelDirection
*= -1.0;
1498 rAlignment
.mfInnerTickDirection
*= -1.0;
1501 rAlignment
.meAlignment
=
1502 (rAlignment
.mfLabelDirection
< 0) ?
1503 LABEL_ALIGN_TOP
: LABEL_ALIGN_BOTTOM
;
1505 if( ( fDeltaX
>0 && m_aScale
.Orientation
== chart2::AxisOrientation_REVERSE
) ||
1506 ( fDeltaX
<0 && m_aScale
.Orientation
== chart2::AxisOrientation_MATHEMATICAL
) )
1507 rAlignment
.meAlignment
=
1508 (rAlignment
.meAlignment
== LABEL_ALIGN_TOP
) ?
1509 LABEL_ALIGN_BOTTOM
: LABEL_ALIGN_TOP
;
1513 TickFactory
* VCartesianAxis::createTickFactory()
1515 return createTickFactory2D();
1518 TickFactory2D
* VCartesianAxis::createTickFactory2D()
1520 AxisLabelAlignment aLabelAlign
= m_aAxisProperties
.maLabelAlignment
;
1521 B2DVector aStart
, aEnd
;
1522 get2DAxisMainLine(aStart
, aEnd
, aLabelAlign
, getAxisIntersectionValue());
1524 B2DVector aLabelLineStart
, aLabelLineEnd
;
1525 get2DAxisMainLine(aLabelLineStart
, aLabelLineEnd
, aLabelAlign
, getLabelLineIntersectionValue());
1526 m_aAxisProperties
.maLabelAlignment
= aLabelAlign
;
1528 return new TickFactory2D( m_aScale
, m_aIncrement
, aStart
, aEnd
, aLabelLineStart
-aStart
);
1531 static void lcl_hideIdenticalScreenValues( TickIter
& rTickIter
)
1533 TickInfo
* pPrevTickInfo
= rTickIter
.firstInfo();
1537 pPrevTickInfo
->bPaintIt
= true;
1538 for( TickInfo
* pTickInfo
= rTickIter
.nextInfo(); pTickInfo
; pTickInfo
= rTickIter
.nextInfo())
1540 pTickInfo
->bPaintIt
= (pTickInfo
->aTickScreenPosition
!= pPrevTickInfo
->aTickScreenPosition
);
1541 pPrevTickInfo
= pTickInfo
;
1545 //'hide' tickmarks with identical screen values in aAllTickInfos
1546 void VCartesianAxis::hideIdenticalScreenValues( TickInfoArraysType
& rTickInfos
) const
1548 if( isComplexCategoryAxis() || isDateAxis() )
1550 sal_Int32 nCount
= rTickInfos
.size();
1551 for( sal_Int32 nN
=0; nN
<nCount
; nN
++ )
1553 PureTickIter
aTickIter( rTickInfos
[nN
] );
1554 lcl_hideIdenticalScreenValues( aTickIter
);
1559 EquidistantTickIter
aTickIter( rTickInfos
, m_aIncrement
, -1 );
1560 lcl_hideIdenticalScreenValues( aTickIter
);
1564 sal_Int32
VCartesianAxis::estimateMaximumAutoMainIncrementCount()
1566 sal_Int32 nRet
= 10;
1568 if( m_nMaximumTextWidthSoFar
==0 && m_nMaximumTextHeightSoFar
==0 )
1571 B2DVector aStart
, aEnd
;
1572 AxisLabelAlignment aLabelAlign
= m_aAxisProperties
.maLabelAlignment
;
1573 get2DAxisMainLine(aStart
, aEnd
, aLabelAlign
, getAxisIntersectionValue());
1574 m_aAxisProperties
.maLabelAlignment
= aLabelAlign
;
1576 sal_Int32 nMaxHeight
= static_cast<sal_Int32
>(fabs(aEnd
.getY()-aStart
.getY()));
1577 sal_Int32 nMaxWidth
= static_cast<sal_Int32
>(fabs(aEnd
.getX()-aStart
.getX()));
1579 sal_Int32 nTotalAvailable
= nMaxHeight
;
1580 sal_Int32 nSingleNeeded
= m_nMaximumTextHeightSoFar
;
1581 sal_Int32 nMaxSameLabel
= 0;
1583 // tdf#48041: do not duplicate the value labels because of rounding
1584 if (m_aAxisProperties
.m_nAxisType
!= css::chart2::AxisType::DATE
)
1586 FixedNumberFormatter
aFixedNumberFormatterTest(m_xNumberFormatsSupplier
, m_aAxisLabelProperties
.nNumberFormatKey
);
1587 OUString sPreviousValueLabel
;
1588 sal_Int32 nSameLabel
= 0;
1589 for (sal_Int32 nLabel
= 0; nLabel
< static_cast<sal_Int32
>(m_aAllTickInfos
[0].size()); ++nLabel
)
1591 Color nColor
= COL_AUTO
;
1592 bool bHasColor
= false;
1593 OUString sValueLabel
= aFixedNumberFormatterTest
.getFormattedString(m_aAllTickInfos
[0][nLabel
].fScaledTickValue
, nColor
, bHasColor
);
1594 if (sValueLabel
== sPreviousValueLabel
)
1597 if (nSameLabel
> nMaxSameLabel
)
1598 nMaxSameLabel
= nSameLabel
;
1602 sPreviousValueLabel
= sValueLabel
;
1605 //for horizontal axis:
1606 if( (m_nDimensionIndex
== 0 && !m_aAxisProperties
.m_bSwapXAndY
)
1607 || (m_nDimensionIndex
== 1 && m_aAxisProperties
.m_bSwapXAndY
) )
1609 nTotalAvailable
= nMaxWidth
;
1610 nSingleNeeded
= m_nMaximumTextWidthSoFar
;
1613 if( nSingleNeeded
>0 )
1614 nRet
= nTotalAvailable
/nSingleNeeded
;
1616 if ( nMaxSameLabel
> 0 )
1618 sal_Int32 nRetNoSameLabel
= m_aAllTickInfos
[0].size() / (nMaxSameLabel
+ 1);
1619 if ( nRet
> nRetNoSameLabel
)
1620 nRet
= nRetNoSameLabel
;
1626 void VCartesianAxis::doStaggeringOfLabels( const AxisLabelProperties
& rAxisLabelProperties
, TickFactory2D
const * pTickFactory2D
)
1628 if( !pTickFactory2D
)
1631 if( isComplexCategoryAxis() )
1633 sal_Int32 nTextLevelCount
= getTextLevelCount();
1634 B2DVector
aCumulatedLabelsDistance(0,0);
1635 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1637 std::unique_ptr
<TickIter
> apTickIter(createLabelTickIterator(nTextLevel
));
1640 double fRotationAngleDegree
= m_aAxisLabelProperties
.fRotationAngleDegree
;
1643 lcl_shiftLabels(*apTickIter
, aCumulatedLabelsDistance
);
1644 //multilevel labels: 0 or 90 by default
1645 if( m_aAxisProperties
.m_bSwapXAndY
)
1646 fRotationAngleDegree
= 90.0;
1648 fRotationAngleDegree
= 0.0;
1650 aCumulatedLabelsDistance
+= lcl_getLabelsDistance(
1651 *apTickIter
, pTickFactory2D
->getDistanceAxisTickToText(m_aAxisProperties
),
1652 fRotationAngleDegree
);
1656 else if (rAxisLabelProperties
.isStaggered())
1658 if( !m_aAllTickInfos
.empty() )
1660 LabelIterator
aInnerIter( m_aAllTickInfos
[0], rAxisLabelProperties
.eStaggering
, true );
1661 LabelIterator
aOuterIter( m_aAllTickInfos
[0], rAxisLabelProperties
.eStaggering
, false );
1663 lcl_shiftLabels( aOuterIter
1664 , lcl_getLabelsDistance( aInnerIter
1665 , pTickFactory2D
->getDistanceAxisTickToText( m_aAxisProperties
), 0.0 ) );
1670 void VCartesianAxis::createLabels()
1672 if( !prepareShapeCreation() )
1676 if (!m_aAxisProperties
.m_bDisplayLabels
)
1679 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1680 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1682 //get the transformed screen values for all tickmarks in aAllTickInfos
1683 pTickFactory2D
->updateScreenValues( m_aAllTickInfos
);
1684 //'hide' tickmarks with identical screen values in aAllTickInfos
1685 hideIdenticalScreenValues( m_aAllTickInfos
);
1687 removeTextShapesFromTicks();
1689 //create tick mark text shapes
1690 sal_Int32 nTextLevelCount
= getTextLevelCount();
1691 sal_Int32 nScreenDistanceBetweenTicks
= -1;
1692 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1694 std::unique_ptr
< TickIter
> apTickIter(createLabelTickIterator( nTextLevel
));
1699 nScreenDistanceBetweenTicks
= TickFactory2D::getTickScreenDistance(*apTickIter
);
1700 if( nTextLevelCount
>1 )
1701 nScreenDistanceBetweenTicks
*=2; //the above used tick iter does contain also the sub ticks -> thus the given distance is only the half
1704 AxisLabelProperties
aComplexProps(m_aAxisLabelProperties
);
1705 if( m_aAxisProperties
.m_bComplexCategories
)
1707 aComplexProps
.bLineBreakAllowed
= true;
1708 aComplexProps
.bOverlapAllowed
= aComplexProps
.fRotationAngleDegree
!= 0.0;
1709 if( nTextLevel
> 0 )
1711 //multilevel labels: 0 or 90 by default
1712 if( m_aAxisProperties
.m_bSwapXAndY
)
1713 aComplexProps
.fRotationAngleDegree
= 90.0;
1715 aComplexProps
.fRotationAngleDegree
= 0.0;
1718 AxisLabelProperties
& rAxisLabelProperties
= m_aAxisProperties
.m_bComplexCategories
? aComplexProps
: m_aAxisLabelProperties
;
1719 while (!createTextShapes(m_xTextTarget
, *apTickIter
, rAxisLabelProperties
,
1720 pTickFactory2D
, nScreenDistanceBetweenTicks
))
1725 doStaggeringOfLabels( m_aAxisLabelProperties
, pTickFactory2D
);
1728 void VCartesianAxis::createMaximumLabels()
1730 TrueGuard
aRecordMaximumTextSize(m_bRecordMaximumTextSize
);
1732 if( !prepareShapeCreation() )
1736 if (!m_aAxisProperties
.m_bDisplayLabels
)
1739 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1740 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1742 //get the transformed screen values for all tickmarks in aAllTickInfos
1743 pTickFactory2D
->updateScreenValues( m_aAllTickInfos
);
1745 //create tick mark text shapes
1746 //@todo: iterate through all tick depth which should be labeled
1748 AxisLabelProperties
aAxisLabelProperties( m_aAxisLabelProperties
);
1749 if( isAutoStaggeringOfLabelsAllowed( aAxisLabelProperties
, pTickFactory2D
->isHorizontalAxis(), pTickFactory2D
->isVerticalAxis() ) )
1750 aAxisLabelProperties
.eStaggering
= AxisLabelStaggering::StaggerEven
;
1752 aAxisLabelProperties
.bOverlapAllowed
= true;
1753 aAxisLabelProperties
.bLineBreakAllowed
= false;
1754 sal_Int32 nTextLevelCount
= getTextLevelCount();
1755 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1757 std::unique_ptr
< TickIter
> apTickIter(createMaximumLabelTickIterator( nTextLevel
));
1760 while (!createTextShapes(m_xTextTarget
, *apTickIter
, aAxisLabelProperties
,
1761 pTickFactory2D
, -1))
1766 doStaggeringOfLabels( aAxisLabelProperties
, pTickFactory2D
);
1769 void VCartesianAxis::updatePositions()
1771 //update positions of labels
1772 if (!m_aAxisProperties
.m_bDisplayLabels
)
1775 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1776 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1778 //update positions of all existing text shapes
1779 pTickFactory2D
->updateScreenValues( m_aAllTickInfos
);
1782 for (auto const& tickInfos
: m_aAllTickInfos
)
1784 for (auto const& tickInfo
: tickInfos
)
1786 Reference
< drawing::XShape
> xShape2DText(tickInfo
.xTextShape
);
1787 if( xShape2DText
.is() )
1789 B2DVector
aTextToTickDistance( pTickFactory2D
->getDistanceAxisTickToText( m_aAxisProperties
, true ) );
1790 B2DVector
aTickScreenPos2D(tickInfo
.aTickScreenPosition
);
1791 aTickScreenPos2D
+= aTextToTickDistance
;
1792 awt::Point
aAnchorScreenPosition2D(
1793 static_cast<sal_Int32
>(aTickScreenPos2D
.getX())
1794 ,static_cast<sal_Int32
>(aTickScreenPos2D
.getY()));
1796 double fRotationAngleDegree
= m_aAxisLabelProperties
.fRotationAngleDegree
;
1799 //multilevel labels: 0 or 90 by default
1800 if( pTickFactory2D
->isHorizontalAxis() )
1801 fRotationAngleDegree
= 0.0;
1803 fRotationAngleDegree
= 90;
1806 // #i78696# use mathematically correct rotation now
1807 const double fRotationAnglePi(-basegfx::deg2rad(fRotationAngleDegree
));
1808 uno::Any aATransformation
= ShapeFactory::makeTransformation(aAnchorScreenPosition2D
, fRotationAnglePi
);
1811 uno::Reference
< beans::XPropertySet
> xProp( xShape2DText
, uno::UNO_QUERY
);
1816 xProp
->setPropertyValue( "Transformation", aATransformation
);
1818 catch( const uno::Exception
& )
1820 TOOLS_WARN_EXCEPTION("chart2", "" );
1824 //correctPositionForRotation
1825 LabelPositionHelper::correctPositionForRotation( xShape2DText
1826 , m_aAxisProperties
.maLabelAlignment
.meAlignment
, fRotationAngleDegree
, m_aAxisProperties
.m_bComplexCategories
);
1832 doStaggeringOfLabels( m_aAxisLabelProperties
, pTickFactory2D
);
1835 void VCartesianAxis::createTickMarkLineShapes( TickInfoArrayType
& rTickInfos
, const TickmarkProperties
& rTickmarkProperties
, TickFactory2D
const & rTickFactory2D
, bool bOnlyAtLabels
)
1837 sal_Int32 nPointCount
= rTickInfos
.size();
1838 drawing::PointSequenceSequence
aPoints(2*nPointCount
);
1841 for (auto const& tickInfo
: rTickInfos
)
1843 if( !tickInfo
.bPaintIt
)
1846 bool bTicksAtLabels
= ( m_aAxisProperties
.m_eTickmarkPos
!= css::chart::ChartAxisMarkPosition_AT_AXIS
);
1847 double fInnerDirectionSign
= m_aAxisProperties
.maLabelAlignment
.mfInnerTickDirection
;
1848 if( bTicksAtLabels
&& m_aAxisProperties
.m_eLabelPos
== css::chart::ChartAxisLabelPosition_OUTSIDE_END
)
1849 fInnerDirectionSign
*= -1.0;
1850 bTicksAtLabels
= bTicksAtLabels
|| bOnlyAtLabels
;
1851 //add ticks at labels:
1852 rTickFactory2D
.addPointSequenceForTickLine( aPoints
, nN
++, tickInfo
.fScaledTickValue
1853 , fInnerDirectionSign
, rTickmarkProperties
, bTicksAtLabels
);
1854 //add ticks at axis (without labels):
1855 if( !bOnlyAtLabels
&& m_aAxisProperties
.m_eTickmarkPos
== css::chart::ChartAxisMarkPosition_AT_LABELS_AND_AXIS
)
1856 rTickFactory2D
.addPointSequenceForTickLine( aPoints
, nN
++, tickInfo
.fScaledTickValue
1857 , m_aAxisProperties
.maLabelAlignment
.mfInnerTickDirection
, rTickmarkProperties
, !bTicksAtLabels
);
1859 aPoints
.realloc(nN
);
1860 m_pShapeFactory
->createLine2D( m_xGroupShape_Shapes
, aPoints
1861 , &rTickmarkProperties
.aLineProperties
);
1864 void VCartesianAxis::createShapes()
1866 if( !prepareShapeCreation() )
1869 std::unique_ptr
<TickFactory2D
> apTickFactory2D(createTickFactory2D()); // throws on failure
1870 TickFactory2D
* pTickFactory2D
= apTickFactory2D
.get();
1872 //create line shapes
1875 //create extra long ticks to separate complex categories (create them only there where the labels are)
1876 if( isComplexCategoryAxis() )
1878 TickInfoArraysType aComplexTickInfos
;
1879 createAllTickInfosFromComplexCategories( aComplexTickInfos
, true );
1880 pTickFactory2D
->updateScreenValues( aComplexTickInfos
);
1881 hideIdenticalScreenValues( aComplexTickInfos
);
1883 std::vector
<TickmarkProperties
> aTickmarkPropertiesList
;
1884 static const bool bIncludeSpaceBetweenTickAndText
= false;
1885 sal_Int32 nOffset
= static_cast<sal_Int32
>(pTickFactory2D
->getDistanceAxisTickToText( m_aAxisProperties
, false, bIncludeSpaceBetweenTickAndText
).getLength());
1886 sal_Int32 nTextLevelCount
= getTextLevelCount();
1887 for( sal_Int32 nTextLevel
=0; nTextLevel
<nTextLevelCount
; nTextLevel
++ )
1889 std::unique_ptr
< TickIter
> apTickIter(createLabelTickIterator( nTextLevel
));
1892 double fRotationAngleDegree
= m_aAxisLabelProperties
.fRotationAngleDegree
;
1893 if( nTextLevel
> 0 )
1895 //Multi-level Labels: default to 0 or 90
1896 if( m_aAxisProperties
.m_bSwapXAndY
)
1897 fRotationAngleDegree
= 90.0;
1899 fRotationAngleDegree
= 0.0;
1901 B2DVector
aLabelsDistance(lcl_getLabelsDistance(
1902 *apTickIter
, pTickFactory2D
->getDistanceAxisTickToText(m_aAxisProperties
),
1903 fRotationAngleDegree
));
1904 sal_Int32 nCurrentLength
= static_cast<sal_Int32
>(aLabelsDistance
.getLength());
1905 aTickmarkPropertiesList
.push_back( m_aAxisProperties
.makeTickmarkPropertiesForComplexCategories( nOffset
+ nCurrentLength
, 0 ) );
1906 nOffset
+= nCurrentLength
;
1910 sal_Int32 nTickmarkPropertiesCount
= aTickmarkPropertiesList
.size();
1911 TickInfoArraysType::iterator aDepthIter
= aComplexTickInfos
.begin();
1912 const TickInfoArraysType::const_iterator aDepthEnd
= aComplexTickInfos
.end();
1913 for( sal_Int32 nDepth
=0; aDepthIter
!= aDepthEnd
&& nDepth
< nTickmarkPropertiesCount
; ++aDepthIter
, nDepth
++ )
1915 if(nDepth
==0 && !m_aAxisProperties
.m_nMajorTickmarks
)
1917 createTickMarkLineShapes( *aDepthIter
, aTickmarkPropertiesList
[nDepth
], *pTickFactory2D
, true /*bOnlyAtLabels*/ );
1920 //create normal ticks for major and minor intervals
1922 TickInfoArraysType aUnshiftedTickInfos
;
1923 if( m_aScale
.ShiftedCategoryPosition
)// if ShiftedCategoryPosition==true the tickmarks in m_aAllTickInfos are shifted
1925 pTickFactory2D
->getAllTicks( aUnshiftedTickInfos
);
1926 pTickFactory2D
->updateScreenValues( aUnshiftedTickInfos
);
1927 hideIdenticalScreenValues( aUnshiftedTickInfos
);
1929 TickInfoArraysType
& rAllTickInfos
= m_aScale
.ShiftedCategoryPosition
? aUnshiftedTickInfos
: m_aAllTickInfos
;
1931 if (rAllTickInfos
.empty())
1934 sal_Int32 nDepth
= 0;
1935 sal_Int32 nTickmarkPropertiesCount
= m_aAxisProperties
.m_aTickmarkPropertiesList
.size();
1936 for( auto& rTickInfos
: rAllTickInfos
)
1938 if (nDepth
== nTickmarkPropertiesCount
)
1941 createTickMarkLineShapes( rTickInfos
, m_aAxisProperties
.m_aTickmarkPropertiesList
[nDepth
], *pTickFactory2D
, false /*bOnlyAtLabels*/ );
1945 //create axis main lines
1946 //it serves also as the handle shape for the axis selection
1948 drawing::PointSequenceSequence
aPoints(1);
1949 apTickFactory2D
->createPointSequenceForAxisMainLine( aPoints
);
1950 Reference
< drawing::XShape
> xShape
= m_pShapeFactory
->createLine2D(
1951 m_xGroupShape_Shapes
, aPoints
1952 , &m_aAxisProperties
.m_aLineProperties
);
1953 //because of this name this line will be used for marking the axis
1954 ::chart::ShapeFactory::setShapeName( xShape
, "MarkHandles" );
1956 //create an additional line at NULL
1957 if( !AxisHelper::isAxisPositioningEnabled() )
1959 double fExtraLineCrossesOtherAxis
= getExtraLineIntersectionValue();
1960 if (!std::isnan(fExtraLineCrossesOtherAxis
))
1962 B2DVector aStart
, aEnd
;
1963 AxisLabelAlignment aLabelAlign
= m_aAxisProperties
.maLabelAlignment
;
1964 get2DAxisMainLine(aStart
, aEnd
, aLabelAlign
, fExtraLineCrossesOtherAxis
);
1965 m_aAxisProperties
.maLabelAlignment
= aLabelAlign
;
1966 drawing::PointSequenceSequence aPoints
{{
1967 {static_cast<sal_Int32
>(aStart
.getX()), static_cast<sal_Int32
>(aStart
.getY())},
1968 {static_cast<sal_Int32
>(aEnd
.getX()), static_cast<sal_Int32
>(aEnd
.getY())} }};
1969 m_pShapeFactory
->createLine2D(
1970 m_xGroupShape_Shapes
, aPoints
, &m_aAxisProperties
.m_aLineProperties
);
1980 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */