xref: /core/chart2/source/view/axes/VCartesianAxis.cxx (revision 85f88d71967e3ce46be4fa990e7213852eaadd38)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
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>
27 #include <Axis.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 <DataTableView.hxx>
39 
40 #include <comphelper/scopeguard.hxx>
41 
42 #include <basegfx/polygon/b2dpolygon.hxx>
43 #include <basegfx/polygon/b2dpolypolygon.hxx>
44 #include <basegfx/polygon/b2dpolygontools.hxx>
45 #include <basegfx/polygon/b2dpolygonclipper.hxx>
46 #include <basegfx/matrix/b2dhommatrix.hxx>
47 #include <basegfx/numeric/ftools.hxx>
48 
49 #include <algorithm>
50 #include <limits>
51 #include <memory>
52 
53 using namespace ::com::sun::star;
54 using ::com::sun::star::uno::Reference;
55 using ::basegfx::B2DVector;
56 using ::basegfx::B2DPolygon;
57 using ::basegfx::B2DPolyPolygon;
58 
59 namespace chart {
60 
VCartesianAxis(const AxisProperties & rAxisProperties,const Reference<util::XNumberFormatsSupplier> & xNumberFormatsSupplier,sal_Int32 nDimensionIndex,sal_Int32 nDimensionCount,PlottingPositionHelper * pPosHelper)61 VCartesianAxis::VCartesianAxis( const AxisProperties& rAxisProperties
62             , const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier
63             , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount
64             , PlottingPositionHelper* pPosHelper )//takes ownership
65             : VAxisBase( nDimensionIndex, nDimensionCount, rAxisProperties, xNumberFormatsSupplier )
66 {
67     if( pPosHelper )
68         m_pPosHelper = pPosHelper;
69     else
70         m_pPosHelper = new PlottingPositionHelper();
71 }
72 
~VCartesianAxis()73 VCartesianAxis::~VCartesianAxis()
74 {
75     delete m_pPosHelper;
76     m_pPosHelper = nullptr;
77 }
78 
lcl_ResizeTextShapeToFitAvailableSpace(SvxShapeText & rShape2DText,const AxisLabelProperties & rAxisLabelProperties,std::u16string_view rLabel,const tNameSequence & rPropNames,const tAnySequence & rPropValues,const bool bIsHorizontalAxis)79 static void lcl_ResizeTextShapeToFitAvailableSpace( SvxShapeText& rShape2DText,
80                                              const AxisLabelProperties& rAxisLabelProperties,
81                                              std::u16string_view rLabel,
82                                              const tNameSequence& rPropNames,
83                                              const tAnySequence& rPropValues,
84                                              const bool bIsHorizontalAxis )
85 {
86     bool bTextHorizontal = rAxisLabelProperties.m_fRotationAngleDegree != 0.0;
87     bool bIsDirectionVertical = bIsHorizontalAxis && bTextHorizontal;
88     const sal_Int32 nFullSize = bIsDirectionVertical ? rAxisLabelProperties.m_aFontReferenceSize.Height : rAxisLabelProperties.m_aFontReferenceSize.Width;
89 
90     if( !nFullSize || rLabel.empty() )
91         return;
92 
93     const sal_Int32 nAvgCharWidth = rShape2DText.getSize().Width / rLabel.size();
94 
95     sal_Int32 nMaxLabelsSize = bIsDirectionVertical ? rAxisLabelProperties.m_aMaximumSpaceForLabels.Height : rAxisLabelProperties.m_aMaximumSpaceForLabels.Width;
96 
97     awt::Size aSizeAfterRotation = ShapeFactory::getSizeAfterRotation(rShape2DText, rAxisLabelProperties.m_fRotationAngleDegree);
98 
99     const sal_Int32 nTextSize = bIsDirectionVertical ? aSizeAfterRotation.Height : aSizeAfterRotation.Width;
100 
101     if( !nAvgCharWidth )
102         return;
103 
104     static constexpr OUString sDots = u"..."_ustr;
105     const sal_Int32 nCharsToRemove = ( nTextSize - nMaxLabelsSize ) / nAvgCharWidth + 1;
106     sal_Int32 nNewLen = rLabel.size() - nCharsToRemove - sDots.getLength();
107     // Prevent from showing only dots
108     if (nNewLen < 0)
109         nNewLen = ( sal_Int32(rLabel.size()) >= sDots.getLength() ) ? sDots.getLength() : rLabel.size();
110 
111     bool bCrop = nCharsToRemove > 0;
112     if( !bCrop )
113         return;
114 
115     OUString aNewLabel( rLabel.substr( 0, nNewLen ) );
116     if( nNewLen > sDots.getLength() )
117         aNewLabel += sDots;
118     rShape2DText.setString( aNewLabel );
119 
120     PropertyMapper::setMultiProperties( rPropNames, rPropValues, rShape2DText );
121 }
122 
createSingleLabel(const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const awt::Point & rAnchorScreenPosition2D,const OUString & rLabel,const AxisLabelProperties & rAxisLabelProperties,const AxisProperties & rAxisProperties,const tNameSequence & rPropNames,const tAnySequence & rPropValues,const bool bIsHorizontalAxis)123 static rtl::Reference<SvxShapeText> createSingleLabel(
124             const rtl::Reference< SvxShapeGroupAnyD >& xTarget
125           , const awt::Point& rAnchorScreenPosition2D
126           , const OUString& rLabel
127           , const AxisLabelProperties& rAxisLabelProperties
128           , const AxisProperties& rAxisProperties
129           , const tNameSequence& rPropNames
130           , const tAnySequence& rPropValues
131           , const bool bIsHorizontalAxis
132           )
133 {
134     if(rLabel.isEmpty())
135         return nullptr;
136 
137     // #i78696# use mathematically correct rotation now
138     const double fRotationAnglePi(-basegfx::deg2rad(rAxisLabelProperties.m_fRotationAngleDegree));
139     uno::Any aATransformation = ShapeFactory::makeTransformation( rAnchorScreenPosition2D, fRotationAnglePi );
140     OUString aLabel = ShapeFactory::getStackedString( rLabel, rAxisLabelProperties.m_bStackCharacters );
141 
142     rtl::Reference<SvxShapeText> xShape2DText =
143                     ShapeFactory::createText( xTarget, aLabel, rPropNames, rPropValues, aATransformation );
144 
145     if( rAxisProperties.m_bLimitSpaceForLabels )
146         lcl_ResizeTextShapeToFitAvailableSpace(*xShape2DText, rAxisLabelProperties, aLabel, rPropNames, rPropValues, bIsHorizontalAxis);
147 
148     LabelPositionHelper::correctPositionForRotation( xShape2DText
149         , rAxisProperties.maLabelAlignment.meAlignment, rAxisLabelProperties.m_fRotationAngleDegree, rAxisProperties.m_bComplexCategories );
150 
151     return xShape2DText;
152 }
153 
lcl_doesShapeOverlapWithTickmark(SvxShape & rShape,double fRotationAngleDegree,const basegfx::B2DVector & rTickScreenPosition)154 static bool lcl_doesShapeOverlapWithTickmark( SvxShape& rShape
155                        , double fRotationAngleDegree
156                        , const basegfx::B2DVector& rTickScreenPosition )
157 {
158     ::basegfx::B2IRectangle aShapeRect = BaseGFXHelper::makeRectangle(rShape.getPosition(), ShapeFactory::getSizeAfterRotation( rShape, fRotationAngleDegree ));
159 
160     basegfx::B2IVector aPosition(
161         static_cast<sal_Int32>( rTickScreenPosition.getX() )
162         , static_cast<sal_Int32>( rTickScreenPosition.getY() ) );
163     return aShapeRect.isInside(aPosition);
164 }
165 
lcl_getRotatedPolygon(B2DPolygon & aPoly,const::basegfx::B2DRectangle & aRect,const awt::Point & aPos,const double fRotationAngleDegree)166 static void lcl_getRotatedPolygon( B2DPolygon &aPoly, const ::basegfx::B2DRectangle &aRect, const awt::Point &aPos, const double fRotationAngleDegree )
167 {
168     aPoly = basegfx::utils::createPolygonFromRect( aRect );
169 
170     // For rotating the rectangle we use the opposite angle,
171     // since `B2DHomMatrix` class used for
172     // representing the transformation, performs rotations in the positive
173     // direction (from the X axis to the Y axis). However since the coordinate
174     // system used by the chart has the Y-axis pointing downward, a rotation in
175     // the positive direction means a clockwise rotation. On the contrary text
176     // labels are rotated counterclockwise.
177     // The rotation is performed around the top-left vertex of the rectangle
178     // which is then moved to its final position by using the top-left
179     // vertex of the text label bounding box (aPos) as the translation vector.
180     ::basegfx::B2DHomMatrix aMatrix;
181     aMatrix.rotate(-basegfx::deg2rad(fRotationAngleDegree));
182     aMatrix.translate( aPos.X, aPos.Y);
183     aPoly.transform( aMatrix );
184 }
185 
doesOverlap(const rtl::Reference<SvxShapeText> & xShape1,const rtl::Reference<SvxShapeText> & xShape2,double fRotationAngleDegree)186 static bool doesOverlap( const rtl::Reference<SvxShapeText>& xShape1
187                 , const rtl::Reference<SvxShapeText>& xShape2
188                 , double fRotationAngleDegree )
189 {
190     if( !xShape1.is() || !xShape2.is() )
191         return false;
192 
193     ::basegfx::B2DRectangle aRect1( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape1->getSize()));
194     ::basegfx::B2DRectangle aRect2( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape2->getSize()));
195 
196     B2DPolygon aPoly1;
197     B2DPolygon aPoly2;
198     lcl_getRotatedPolygon( aPoly1, aRect1, xShape1->getPosition(), fRotationAngleDegree );
199     lcl_getRotatedPolygon( aPoly2, aRect2, xShape2->getPosition(), fRotationAngleDegree );
200 
201     B2DPolyPolygon aPolyPoly1, aPolyPoly2;
202     aPolyPoly1.append( aPoly1 );
203     aPolyPoly2.append( aPoly2 );
204     B2DPolyPolygon overlapPoly = ::basegfx::utils::clipPolyPolygonOnPolyPolygon( aPolyPoly1, aPolyPoly2, true, false );
205 
206     return (overlapPoly.count() > 0);
207 }
208 
removeShapesAtWrongRhythm(TickIter & rIter,sal_Int32 nCorrectRhythm,sal_Int32 nMaxTickToCheck,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)209 static void removeShapesAtWrongRhythm( TickIter& rIter
210                               , sal_Int32 nCorrectRhythm
211                               , sal_Int32 nMaxTickToCheck
212                               , const rtl::Reference< SvxShapeGroupAnyD >& xTarget )
213 {
214     sal_Int32 nTick = 0;
215     for( TickInfo* pTickInfo = rIter.firstInfo()
216         ; pTickInfo && nTick <= nMaxTickToCheck
217         ; pTickInfo = rIter.nextInfo(), nTick++ )
218     {
219         //remove labels which does not fit into the rhythm
220         if( nTick%nCorrectRhythm != 0)
221         {
222             if(pTickInfo->xTextShape.is())
223             {
224                 xTarget->remove(pTickInfo->xTextShape);
225                 pTickInfo->xTextShape = nullptr;
226             }
227         }
228     }
229 }
230 
231 namespace {
232 
233 /**
234  * If the labels are staggered and bInnerLine is true we iterate through
235  * only those labels that are closer to the diagram.
236  *
237  * If the labels are staggered and bInnerLine is false we iterate through
238  * only those that are farther from the diagram.
239  *
240  * If the labels are not staggered we iterate through all labels.
241  */
242 class LabelIterator : public TickIter
243 {
244 public:
245     LabelIterator( TickInfoArrayType& rTickInfoVector
246             , const AxisLabelStaggering eAxisLabelStaggering
247             , bool bInnerLine );
248 
249     virtual TickInfo*   firstInfo() override;
250     virtual TickInfo*   nextInfo() override;
251 
252 private: //member
253     PureTickIter m_aPureTickIter;
254     const AxisLabelStaggering   m_eAxisLabelStaggering;
255     bool m_bInnerLine;
256 };
257 
258 }
259 
LabelIterator(TickInfoArrayType & rTickInfoVector,const AxisLabelStaggering eAxisLabelStaggering,bool bInnerLine)260 LabelIterator::LabelIterator( TickInfoArrayType& rTickInfoVector
261             , const AxisLabelStaggering eAxisLabelStaggering
262             , bool bInnerLine )
263             : m_aPureTickIter( rTickInfoVector )
264             , m_eAxisLabelStaggering(eAxisLabelStaggering)
265             , m_bInnerLine(bInnerLine)
266 {
267 }
268 
firstInfo()269 TickInfo* LabelIterator::firstInfo()
270 {
271     TickInfo* pTickInfo = m_aPureTickIter.firstInfo();
272     while( pTickInfo && !pTickInfo->xTextShape.is() )
273         pTickInfo = m_aPureTickIter.nextInfo();
274     if(!pTickInfo)
275         return nullptr;
276     if( (m_eAxisLabelStaggering==AxisLabelStaggering::StaggerEven && m_bInnerLine)
277         ||
278         (m_eAxisLabelStaggering==AxisLabelStaggering::StaggerOdd && !m_bInnerLine)
279         )
280     {
281         //skip first label
282         do
283             pTickInfo = m_aPureTickIter.nextInfo();
284         while( pTickInfo && !pTickInfo->xTextShape.is() );
285     }
286     if(!pTickInfo)
287         return nullptr;
288     return pTickInfo;
289 }
290 
nextInfo()291 TickInfo* LabelIterator::nextInfo()
292 {
293     TickInfo* pTickInfo = nullptr;
294     //get next label
295     do
296         pTickInfo = m_aPureTickIter.nextInfo();
297     while( pTickInfo && !pTickInfo->xTextShape.is() );
298 
299     if(  m_eAxisLabelStaggering==AxisLabelStaggering::StaggerEven
300       || m_eAxisLabelStaggering==AxisLabelStaggering::StaggerOdd )
301     {
302         //skip one label
303         do
304             pTickInfo = m_aPureTickIter.nextInfo();
305         while( pTickInfo && !pTickInfo->xTextShape.is() );
306     }
307     return pTickInfo;
308 }
309 
lcl_getLabelsDistance(TickIter & rIter,const B2DVector & rDistanceTickToText,double fRotationAngleDegree)310 static B2DVector lcl_getLabelsDistance( TickIter& rIter, const B2DVector& rDistanceTickToText, double fRotationAngleDegree )
311 {
312     //calculates the height or width of a line of labels
313     //thus a following line of labels can be shifted for that distance
314 
315     B2DVector aRet(0,0);
316 
317     sal_Int32 nDistanceTickToText = static_cast<sal_Int32>( rDistanceTickToText.getLength() );
318     if( nDistanceTickToText==0.0)
319         return aRet;
320 
321     B2DVector aStaggerDirection(rDistanceTickToText);
322     aStaggerDirection.normalize();
323 
324     sal_Int32 nDistance=0;
325     rtl::Reference< SvxShapeText >  xShape2DText;
326     for( TickInfo* pTickInfo = rIter.firstInfo()
327         ; pTickInfo
328         ; pTickInfo = rIter.nextInfo() )
329     {
330         xShape2DText = pTickInfo->xTextShape;
331         if( xShape2DText.is() )
332         {
333             awt::Size aSize = ShapeFactory::getSizeAfterRotation( *xShape2DText, fRotationAngleDegree );
334             if(fabs(aStaggerDirection.getX())>fabs(aStaggerDirection.getY()))
335                 nDistance = std::max(nDistance,aSize.Width);
336             else
337                 nDistance = std::max(nDistance,aSize.Height);
338         }
339     }
340 
341     aRet = aStaggerDirection*nDistance;
342 
343     //add extra distance for vertical distance
344     if(fabs(aStaggerDirection.getX())>fabs(aStaggerDirection.getY()))
345         aRet += rDistanceTickToText;
346 
347     return aRet;
348 }
349 
lcl_shiftLabels(TickIter & rIter,const B2DVector & rStaggerDistance)350 static void lcl_shiftLabels( TickIter& rIter, const B2DVector& rStaggerDistance )
351 {
352     if(rStaggerDistance.getLength()==0.0)
353         return;
354     for( TickInfo* pTickInfo = rIter.firstInfo()
355         ; pTickInfo
356         ; pTickInfo = rIter.nextInfo() )
357     {
358         const rtl::Reference<SvxShapeText>& xShape2DText = pTickInfo->xTextShape;
359         if( xShape2DText.is() )
360         {
361             awt::Point aPos  = xShape2DText->getPosition();
362             aPos.X += static_cast<sal_Int32>(rStaggerDistance.getX());
363             aPos.Y += static_cast<sal_Int32>(rStaggerDistance.getY());
364             xShape2DText->setPosition( aPos );
365         }
366     }
367 }
368 
lcl_hasWordBreak(const rtl::Reference<SvxShapeText> & xShape)369 static bool lcl_hasWordBreak( const rtl::Reference<SvxShapeText>& xShape )
370 {
371     if (!xShape.is())
372         return false;
373 
374     SvxTextEditSource* pTextEditSource = dynamic_cast<SvxTextEditSource*>(xShape->GetEditSource());
375     if (!pTextEditSource)
376         return false;
377 
378     pTextEditSource->UpdateOutliner();
379     SvxTextForwarder* pTextForwarder = pTextEditSource->GetTextForwarder();
380     if (!pTextForwarder)
381         return false;
382 
383     sal_Int32 nParaCount = pTextForwarder->GetParagraphCount();
384     for ( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
385     {
386         sal_Int32 nLineCount = pTextForwarder->GetLineCount( nPara );
387         for ( sal_Int32 nLine = 0; nLine < nLineCount; ++nLine )
388         {
389             sal_Int32 nLineStart = 0;
390             sal_Int32 nLineEnd = 0;
391             pTextForwarder->GetLineBoundaries( nLineStart, nLineEnd, nPara, nLine );
392             assert(nLineStart >= 0);
393             sal_Int32 nWordStart = 0;
394             sal_Int32 nWordEnd = 0;
395             if ( pTextForwarder->GetWordIndices( nPara, nLineStart, nWordStart, nWordEnd ) &&
396                  ( nWordStart != nLineStart ) )
397             {
398                 return true;
399             }
400         }
401     }
402 
403     return false;
404 }
405 
getTextLabelString(const FixedNumberFormatter & rFixedNumberFormatter,const uno::Sequence<OUString> * pCategories,const TickInfo * pTickInfo,bool bComplexCat,Color & rExtraColor,bool & rHasExtraColor)406 static OUString getTextLabelString(
407     const FixedNumberFormatter& rFixedNumberFormatter, const uno::Sequence<OUString>* pCategories,
408     const TickInfo* pTickInfo, bool bComplexCat, Color& rExtraColor, bool& rHasExtraColor )
409 {
410     if (pCategories)
411     {
412         // This is a normal category axis.  Get the label string from the
413         // label string array.
414         sal_Int32 nIndex = static_cast<sal_Int32>(pTickInfo->getUnscaledTickValue()) - 1; //first category (index 0) matches with real number 1.0
415         if( nIndex>=0 && nIndex<pCategories->getLength() )
416             return (*pCategories)[nIndex];
417 
418         return OUString();
419     }
420     else if (bComplexCat)
421     {
422         // This is a complex category axis.  The label is stored in the tick.
423         return pTickInfo->aText;
424     }
425 
426     // This is a numeric axis.  Format the original tick value per number format.
427     return rFixedNumberFormatter.getFormattedString(pTickInfo->getUnscaledTickValue(), rExtraColor, rHasExtraColor);
428 }
429 
getAxisLabelProperties(tNameSequence & rPropNames,tAnySequence & rPropValues,const AxisProperties & rAxisProp,const AxisLabelProperties & rAxisLabelProp,sal_Int32 nLimitedSpaceForText,bool bLimitedHeight)430 static void getAxisLabelProperties(
431     tNameSequence& rPropNames, tAnySequence& rPropValues, const AxisProperties& rAxisProp,
432     const AxisLabelProperties& rAxisLabelProp,
433     sal_Int32 nLimitedSpaceForText, bool bLimitedHeight )
434 {
435     Reference<beans::XPropertySet> xProps(rAxisProp.m_xAxisModel);
436 
437     PropertyMapper::getTextLabelMultiPropertyLists(
438         xProps, rPropNames, rPropValues, false, nLimitedSpaceForText, bLimitedHeight, false);
439 
440     LabelPositionHelper::doDynamicFontResize(
441         rPropValues, rPropNames, xProps, rAxisLabelProp.m_aFontReferenceSize);
442 
443     LabelPositionHelper::changeTextAdjustment(
444         rPropValues, rPropNames, rAxisProp.maLabelAlignment.meAlignment);
445 }
446 
447 namespace {
448 
449 /**
450  * Iterate through only 3 ticks including the one that has the longest text
451  * length.  When the first tick has the longest text, it iterates through
452  * the first 3 ticks.  Otherwise it iterates through 3 ticks such that the
453  * 2nd tick is the one with the longest text.
454  */
455 class MaxLabelTickIter : public TickIter
456 {
457 public:
458     MaxLabelTickIter( TickInfoArrayType& rTickInfoVector, size_t nLongestLabelIndex );
459 
460     virtual TickInfo* firstInfo() override;
461     virtual TickInfo* nextInfo() override;
462 
463 private:
464     TickInfoArrayType& m_rTickInfoVector;
465     std::vector<size_t> m_aValidIndices;
466     size_t m_nCurrentIndex;
467 };
468 
469 }
470 
MaxLabelTickIter(TickInfoArrayType & rTickInfoVector,size_t nLongestLabelIndex)471 MaxLabelTickIter::MaxLabelTickIter(
472     TickInfoArrayType& rTickInfoVector, size_t nLongestLabelIndex ) :
473     m_rTickInfoVector(rTickInfoVector), m_nCurrentIndex(0)
474 {
475     assert(!rTickInfoVector.empty()); // should be checked by the caller.
476     assert(nLongestLabelIndex < rTickInfoVector.size());
477 
478     size_t nMaxIndex = m_rTickInfoVector.size()-1;
479     if (nLongestLabelIndex >= nMaxIndex-1)
480         nLongestLabelIndex = 0;
481 
482     if (nLongestLabelIndex > 0)
483         m_aValidIndices.push_back(nLongestLabelIndex-1);
484 
485     m_aValidIndices.push_back(nLongestLabelIndex);
486 
487     while (m_aValidIndices.size() < 3)
488     {
489         ++nLongestLabelIndex;
490         if (nLongestLabelIndex > nMaxIndex)
491             break;
492 
493         m_aValidIndices.push_back(nLongestLabelIndex);
494     }
495 }
496 
firstInfo()497 TickInfo* MaxLabelTickIter::firstInfo()
498 {
499     m_nCurrentIndex = 0;
500     if (m_nCurrentIndex < m_aValidIndices.size())
501         return &m_rTickInfoVector[m_aValidIndices[m_nCurrentIndex]];
502     return nullptr;
503 }
504 
nextInfo()505 TickInfo* MaxLabelTickIter::nextInfo()
506 {
507     m_nCurrentIndex++;
508     if (m_nCurrentIndex < m_aValidIndices.size())
509         return &m_rTickInfoVector[m_aValidIndices[m_nCurrentIndex]];
510     return nullptr;
511 }
512 
isBreakOfLabelsAllowed(const AxisLabelProperties & rAxisLabelProperties,bool bIsHorizontalAxis,bool bIsVerticalAxis) const513 bool VCartesianAxis::isBreakOfLabelsAllowed(
514     const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis) const
515 {
516     if( m_aTextLabels.getLength() > 100 )
517         return false;
518     if( !rAxisLabelProperties.m_bLineBreakAllowed )
519         return false;
520     if( rAxisLabelProperties.m_bStackCharacters )
521         return false;
522     //no break for value axis
523     if( !m_bUseTextLabels )
524         return false;
525     if( !( rAxisLabelProperties.m_fRotationAngleDegree == 0.0 ||
526            rAxisLabelProperties.m_fRotationAngleDegree == 90.0 ||
527            rAxisLabelProperties.m_fRotationAngleDegree == 270.0 ) )
528         return false;
529     //no break for complex vertical category axis
530     if( !m_aAxisProperties.m_bSwapXAndY )
531         return bIsHorizontalAxis;
532     else if( m_aAxisProperties.m_bSwapXAndY && !m_aAxisProperties.m_bComplexCategories )
533         return bIsVerticalAxis;
534     else
535         return false;
536 }
537 namespace{
538 
canAutoAdjustLabelPlacement(const AxisLabelProperties & rAxisLabelProperties,bool bIsHorizontalAxis,bool bIsVerticalAxis)539 bool canAutoAdjustLabelPlacement(
540     const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis)
541 {
542     // joined prerequisite checks for auto rotate and auto stagger
543     if( rAxisLabelProperties.m_bOverlapAllowed )
544         return false;
545     if( rAxisLabelProperties.m_bLineBreakAllowed ) // auto line break may conflict with...
546         return false;
547     if( rAxisLabelProperties.m_fRotationAngleDegree != 0.0 )
548         return false;
549     // automatic adjusting labels only works for
550     // horizontal axis with horizontal text
551     // or vertical axis with vertical text
552     if( bIsHorizontalAxis )
553         return !rAxisLabelProperties.m_bStackCharacters;
554     if( bIsVerticalAxis )
555         return rAxisLabelProperties.m_bStackCharacters;
556     return false;
557 }
558 
isAutoStaggeringOfLabelsAllowed(const AxisLabelProperties & rAxisLabelProperties,bool bIsHorizontalAxis,bool bIsVerticalAxis)559 bool isAutoStaggeringOfLabelsAllowed(
560     const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis )
561 {
562     if( rAxisLabelProperties.m_eStaggering != AxisLabelStaggering::StaggerAuto )
563         return false;
564     return canAutoAdjustLabelPlacement(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis);
565 }
566 
567 // make clear that we check for auto rotation prerequisites
568 const auto& isAutoRotatingOfLabelsAllowed = canAutoAdjustLabelPlacement;
569 
570 } // namespace
createAllTickInfosFromComplexCategories(TickInfoArraysType & rAllTickInfos,bool bShiftedPosition)571 void VCartesianAxis::createAllTickInfosFromComplexCategories( TickInfoArraysType& rAllTickInfos, bool bShiftedPosition )
572 {
573     //no minor tickmarks will be generated!
574     //order is: inner labels first , outer labels last (that is different to all other TickIter cases)
575     if(!bShiftedPosition)
576     {
577         rAllTickInfos.clear();
578         sal_Int32 nLevel=0;
579         sal_Int32 nLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount();
580         for( ; nLevel<nLevelCount; nLevel++ )
581         {
582             TickInfoArrayType aTickInfoVector;
583             const std::vector<ComplexCategory>* pComplexCategories =
584                 m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoriesByLevel(nLevel);
585 
586             if (!pComplexCategories)
587                 continue;
588 
589             sal_Int32 nCatIndex = 0;
590 
591             for (auto const& complexCategory : *pComplexCategories)
592             {
593                 TickInfo aTickInfo(nullptr);
594                 sal_Int32 nCount = complexCategory.Count;
595                 if( nCatIndex + 1.0 + nCount >= m_aScale.Maximum )
596                 {
597                     nCount = static_cast<sal_Int32>(m_aScale.Maximum - 1.0 - nCatIndex);
598                     if( nCount <= 0 )
599                         nCount = 1;
600                 }
601                 aTickInfo.fScaledTickValue = nCatIndex + 1.0 + nCount/2.0;
602                 aTickInfo.nFactorForLimitedTextWidth = nCount;
603                 aTickInfo.aText = complexCategory.Text;
604                 aTickInfoVector.push_back(aTickInfo);
605                 nCatIndex += nCount;
606                 if( nCatIndex + 1.0 >= m_aScale.Maximum )
607                     break;
608             }
609             rAllTickInfos.push_back(std::move(aTickInfoVector));
610         }
611     }
612     else //bShiftedPosition==false
613     {
614         rAllTickInfos.clear();
615         sal_Int32 nLevel=0;
616         sal_Int32 nLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount();
617         for( ; nLevel<nLevelCount; nLevel++ )
618         {
619             TickInfoArrayType aTickInfoVector;
620             const std::vector<ComplexCategory>* pComplexCategories =
621                 m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoriesByLevel(nLevel);
622             sal_Int32 nCatIndex = 0;
623             if (pComplexCategories)
624             {
625                 for (auto const& complexCategory : *pComplexCategories)
626                 {
627                     TickInfo aTickInfo(nullptr);
628                     aTickInfo.fScaledTickValue = nCatIndex + 1.0;
629                     aTickInfoVector.push_back(aTickInfo);
630                     nCatIndex += complexCategory.Count;
631                     if( nCatIndex + 1.0 > m_aScale.Maximum )
632                         break;
633                 }
634             }
635 
636             //fill up with single ticks until maximum scale
637             while( nCatIndex + 1.0 < m_aScale.Maximum )
638             {
639                 TickInfo aTickInfo(nullptr);
640                 aTickInfo.fScaledTickValue = nCatIndex + 1.0;
641                 aTickInfoVector.push_back(aTickInfo);
642                 nCatIndex ++;
643                 if( nLevel>0 )
644                     break;
645             }
646             //add an additional tick at the end
647             {
648                 TickInfo aTickInfo(nullptr);
649                 aTickInfo.fScaledTickValue = m_aScale.Maximum;
650                 aTickInfoVector.push_back(aTickInfo);
651             }
652             rAllTickInfos.push_back(std::move(aTickInfoVector));
653         }
654     }
655 }
656 
createAllTickInfos(TickInfoArraysType & rAllTickInfos)657 void VCartesianAxis::createAllTickInfos( TickInfoArraysType& rAllTickInfos )
658 {
659     if( isComplexCategoryAxis() )
660         createAllTickInfosFromComplexCategories( rAllTickInfos, false );
661     else
662         VAxisBase::createAllTickInfos(rAllTickInfos);
663 }
664 
createLabelTickIterator(sal_Int32 nTextLevel)665 TickIter* VCartesianAxis::createLabelTickIterator( sal_Int32 nTextLevel )
666 {
667     if( nTextLevel>=0 && o3tl::make_unsigned(nTextLevel) < m_aAllTickInfos.size() )
668         return new PureTickIter( m_aAllTickInfos[nTextLevel] );
669     return nullptr;
670 }
671 
createMaximumLabelTickIterator(sal_Int32 nTextLevel)672 TickIter* VCartesianAxis::createMaximumLabelTickIterator( sal_Int32 nTextLevel )
673 {
674     if( isComplexCategoryAxis() || isDateAxis() )
675     {
676         return createLabelTickIterator( nTextLevel ); //mmmm maybe todo: create less than all texts here
677     }
678     else
679     {
680         if(nTextLevel==0)
681         {
682             if( !m_aAllTickInfos.empty() )
683             {
684                 size_t nLongestLabelIndex = m_bUseTextLabels ? getIndexOfLongestLabel(m_aTextLabels) : 0;
685                 if (nLongestLabelIndex >= m_aAllTickInfos[0].size())
686                     return nullptr;
687 
688                 return new MaxLabelTickIter( m_aAllTickInfos[0], nLongestLabelIndex );
689             }
690         }
691     }
692     return nullptr;
693 }
694 
getTextLevelCount() const695 sal_Int32 VCartesianAxis::getTextLevelCount() const
696 {
697     sal_Int32 nTextLevelCount = 1;
698     if( isComplexCategoryAxis() )
699         nTextLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount();
700     return nTextLevelCount;
701 }
702 
createTextShapes(const rtl::Reference<SvxShapeGroupAnyD> & xTarget,TickIter & rTickIter,AxisLabelProperties & rAxisLabelProperties,TickFactory2D const * pTickFactory,sal_Int32 nScreenDistanceBetweenTicks)703 bool VCartesianAxis::createTextShapes(
704     const rtl::Reference< SvxShapeGroupAnyD >& xTarget, TickIter& rTickIter,
705     AxisLabelProperties& rAxisLabelProperties, TickFactory2D const * pTickFactory,
706     sal_Int32 nScreenDistanceBetweenTicks )
707 {
708     const bool bIsHorizontalAxis = pTickFactory->isHorizontalAxis();
709     const bool bIsVerticalAxis = pTickFactory->isVerticalAxis();
710 
711     if( m_bUseTextLabels && (m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_NEAR_AXIS ||
712         m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START))
713     {
714         if (bIsHorizontalAxis)
715         {
716             rAxisLabelProperties.m_aMaximumSpaceForLabels.Y = pTickFactory->getXaxisStartPos().getY();
717             rAxisLabelProperties.m_aMaximumSpaceForLabels.Height = rAxisLabelProperties.m_aFontReferenceSize.Height - rAxisLabelProperties.m_aMaximumSpaceForLabels.Y;
718         }
719         else if (bIsVerticalAxis)
720         {
721             rAxisLabelProperties.m_aMaximumSpaceForLabels.X = 0;
722             rAxisLabelProperties.m_aMaximumSpaceForLabels.Width = pTickFactory->getXaxisStartPos().getX();
723         }
724     }
725 
726     bool bIsBreakOfLabelsAllowed = isBreakOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis );
727     if (!bIsBreakOfLabelsAllowed &&
728         !isAutoStaggeringOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) &&
729         !rAxisLabelProperties.isStaggered())
730     {
731         return createTextShapesSimple(xTarget, rTickIter, rAxisLabelProperties, pTickFactory);
732     }
733 
734     FixedNumberFormatter aFixedNumberFormatter(
735                 m_xNumberFormatsSupplier, rAxisLabelProperties.m_nNumberFormatKey );
736 
737     bool bIsStaggered = rAxisLabelProperties.isStaggered();
738     B2DVector aTextToTickDistance = pTickFactory->getDistanceAxisTickToText(m_aAxisProperties, true);
739     sal_Int32 nLimitedSpaceForText = -1;
740 
741     if (bIsBreakOfLabelsAllowed)
742     {
743         if (!m_aAxisProperties.m_bLimitSpaceForLabels)
744         {
745             basegfx::B2DVector nDeltaVector = pTickFactory->getXaxisEndPos() - pTickFactory->getXaxisStartPos();
746             nLimitedSpaceForText = nDeltaVector.getX();
747         }
748         if (nScreenDistanceBetweenTicks > 0)
749             nLimitedSpaceForText = nScreenDistanceBetweenTicks;
750 
751         if( bIsStaggered )
752             nLimitedSpaceForText *= 2;
753 
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;
757             if(!nReduce)
758                 nReduce = 1;
759             nLimitedSpaceForText -= nReduce;
760         }
761 
762         // recalculate the nLimitedSpaceForText in case of 90 and 270 degree if the text break is true
763         if ( rAxisLabelProperties.m_fRotationAngleDegree == 90.0 || rAxisLabelProperties.m_fRotationAngleDegree == 270.0 )
764         {
765             nLimitedSpaceForText = rAxisLabelProperties.m_aMaximumSpaceForLabels.Height;
766             m_aAxisProperties.m_bLimitSpaceForLabels = false;
767         }
768 
769         // recalculate the nLimitedSpaceForText in case of vertical category axis if the text break is true
770         if ( m_aAxisProperties.m_bSwapXAndY && bIsVerticalAxis && rAxisLabelProperties.m_fRotationAngleDegree == 0.0 )
771         {
772             nLimitedSpaceForText = pTickFactory->getXaxisStartPos().getX();
773             m_aAxisProperties.m_bLimitSpaceForLabels = false;
774         }
775     }
776 
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;
782 
783     bool bLimitedHeight;
784     if( !m_aAxisProperties.m_bSwapXAndY )
785         bLimitedHeight = fabs(aTextToTickDistance.getX()) > fabs(aTextToTickDistance.getY());
786     else
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);
792 
793     uno::Any* pColorAny = PropertyMapper::getValuePointer(aPropValues,aPropNames,u"CharColor");
794     Color nColor = COL_AUTO;
795     if(pColorAny)
796         *pColorAny >>= nColor;
797 
798     uno::Any* pLimitedSpaceAny = PropertyMapper::getValuePointerForLimitedSpace(aPropValues,aPropNames,bLimitedHeight);
799 
800     const TickInfo* pPreviousVisibleTickInfo = nullptr;
801     const TickInfo* pPREPreviousVisibleTickInfo = nullptr;
802     sal_Int32 nTick = 0;
803     for( TickInfo* pTickInfo = rTickIter.firstInfo()
804         ; pTickInfo
805         ; pTickInfo = rTickIter.nextInfo(), nTick++ )
806     {
807         const TickInfo* pLastVisibleNeighbourTickInfo = bIsStaggered ?
808                     pPREPreviousVisibleTickInfo : pPreviousVisibleTickInfo;
809 
810         //don't create labels which does not fit into the rhythm
811         if( nTick%rAxisLabelProperties.m_nRhythm != 0 )
812             continue;
813 
814         //don't create labels for invisible ticks
815         if( !pTickInfo->bPaintIt )
816             continue;
817 
818         if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
819         {
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.
823 
824             if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
825                        , rAxisLabelProperties.m_fRotationAngleDegree
826                        , pTickInfo->aTickScreenPosition ) )
827             {
828                 // This tick overlaps with its neighbor.  Try to stagger (if
829                 // auto staggering is allowed) to avoid overlapping.
830 
831                 bool bOverlapsAfterAutoStagger = true;
832                 if( !bIsStaggered && isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis ) )
833                 {
834                     bIsStaggered = true;
835                     rAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
836                     pLastVisibleNeighbourTickInfo = pPREPreviousVisibleTickInfo;
837                     if( !pLastVisibleNeighbourTickInfo ||
838                         !lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
839                                 , rAxisLabelProperties.m_fRotationAngleDegree
840                                 , pTickInfo->aTickScreenPosition ) )
841                         bOverlapsAfterAutoStagger = false;
842                 }
843 
844                 if (bOverlapsAfterAutoStagger)
845                 {
846                     // Still overlaps with its neighbor even after staggering.
847                     // Increment the visible tick intervals (if that's
848                     // allowed) and start over.
849 
850                     rAxisLabelProperties.m_nRhythm++;
851                     removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
852                     return false;
853                 }
854             }
855         }
856 
857         bool bHasExtraColor=false;
858         Color nExtraColor;
859 
860         OUString aLabel = getTextLabelString(
861             aFixedNumberFormatter, pCategories, pTickInfo, isComplexCategoryAxis(),
862             nExtraColor, bHasExtraColor);
863 
864         if(pColorAny)
865             *pColorAny <<= bHasExtraColor?nExtraColor:nColor;
866         if(pLimitedSpaceAny)
867             *pLimitedSpaceAny <<= sal_Int32(nLimitedSpaceForText*pTickInfo->nFactorForLimitedTextWidth);
868 
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()));
874 
875         //create single label
876         if(!pTickInfo->xTextShape.is())
877         {
878             pTickInfo->xTextShape = createSingleLabel( xTarget
879                                     , aAnchorScreenPosition2D, aLabel
880                                     , rAxisLabelProperties, m_aAxisProperties
881                                     , aPropNames, aPropValues, bIsHorizontalAxis );
882         }
883         if(!pTickInfo->xTextShape.is())
884             continue;
885 
886         recordMaximumTextSize( *pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree );
887 
888         // Label has multiple lines and the words are broken
889         if (nLimitedSpaceForText > 0
890                 && !rAxisLabelProperties.m_bOverlapAllowed
891                 && rAxisLabelProperties.m_fRotationAngleDegree == 0.0
892                 && nTick > 0
893                 && lcl_hasWordBreak(pTickInfo->xTextShape))
894         {
895             // Label has multiple lines and belongs to a complex category
896             // axis. Rotate 90 degrees to try to avoid overlaps.
897             if ( m_aAxisProperties.m_bComplexCategories )
898             {
899                 rAxisLabelProperties.m_fRotationAngleDegree = 90;
900             }
901             rAxisLabelProperties.m_bLineBreakAllowed = false;
902             m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree;
903             removeTextShapesFromTicks();
904             return false;
905         }
906 
907         //if NO OVERLAP -> remove overlapping shapes
908         if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
909         {
910             // Check if the label still overlaps with its neighbor.
911             if( doesOverlap( pLastVisibleNeighbourTickInfo->xTextShape, pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree ) )
912             {
913                 // It overlaps.  Check if staggering helps.
914                 bool bOverlapsAfterAutoStagger = true;
915                 if( !bIsStaggered && isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis ) )
916                 {
917                     // Compatibility option: starting from LibreOffice 5.1 the rotated
918                     // layout is preferred to staggering for axis labels.
919                     if( !isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis)
920                         || m_aAxisProperties.m_bTryStaggeringFirst )
921                     {
922                         bIsStaggered = true;
923                         rAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
924                         pLastVisibleNeighbourTickInfo = pPREPreviousVisibleTickInfo;
925                         if( !pLastVisibleNeighbourTickInfo ||
926                             !lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
927                                 , rAxisLabelProperties.m_fRotationAngleDegree
928                                 , pTickInfo->aTickScreenPosition ) )
929                             bOverlapsAfterAutoStagger = false;
930                     }
931                 }
932 
933                 if (bOverlapsAfterAutoStagger)
934                 {
935                     // Staggering didn't solve the overlap.
936                     if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) )
937                     {
938                         // Try auto-rotating the labels at 45 degrees and
939                         // start over.  This rotation angle will be stored for
940                         // all future text shape creation runs.
941                         // The nRhythm parameter is reset to 1 since the layout
942                         // used for text labels is changed.
943                         rAxisLabelProperties.autoRotate45();
944                         m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree; // Store it for future runs.
945                         removeTextShapesFromTicks();
946                         rAxisLabelProperties.m_nRhythm = 1;
947                         return false;
948                     }
949 
950                     // Try incrementing the tick interval and start over.
951                     rAxisLabelProperties.m_nRhythm++;
952                     removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
953                     return false;
954                 }
955             }
956         }
957 
958         pPREPreviousVisibleTickInfo = pPreviousVisibleTickInfo;
959         pPreviousVisibleTickInfo = pTickInfo;
960     }
961     return true;
962 }
963 
createTextShapesSimple(const rtl::Reference<SvxShapeGroupAnyD> & xTarget,TickIter & rTickIter,AxisLabelProperties & rAxisLabelProperties,TickFactory2D const * pTickFactory)964 bool VCartesianAxis::createTextShapesSimple(
965     const rtl::Reference< SvxShapeGroupAnyD >& xTarget, TickIter& rTickIter,
966     AxisLabelProperties& rAxisLabelProperties, TickFactory2D const * pTickFactory )
967 {
968     FixedNumberFormatter aFixedNumberFormatter(
969                 m_xNumberFormatsSupplier, rAxisLabelProperties.m_nNumberFormatKey );
970 
971     const bool bIsHorizontalAxis = pTickFactory->isHorizontalAxis();
972     const bool bIsVerticalAxis = pTickFactory->isVerticalAxis();
973     B2DVector aTextToTickDistance = pTickFactory->getDistanceAxisTickToText(m_aAxisProperties, true);
974 
975      // Stores an array of text label strings in case of a normal
976      // (non-complex) category axis.
977     const uno::Sequence<OUString>* pCategories = nullptr;
978     if( m_bUseTextLabels && !m_aAxisProperties.m_bComplexCategories )
979         pCategories = &m_aTextLabels;
980 
981     bool bLimitedHeight = fabs(aTextToTickDistance.getX()) > fabs(aTextToTickDistance.getY());
982 
983     //prepare properties for multipropertyset-interface of shape
984     tNameSequence aPropNames;
985     tAnySequence aPropValues;
986     getAxisLabelProperties(aPropNames, aPropValues, m_aAxisProperties, rAxisLabelProperties, -1, bLimitedHeight);
987 
988     uno::Any* pColorAny = PropertyMapper::getValuePointer(aPropValues,aPropNames,u"CharColor");
989     Color nColor = COL_AUTO;
990     if(pColorAny)
991         *pColorAny >>= nColor;
992 
993     uno::Any* pLimitedSpaceAny = PropertyMapper::getValuePointerForLimitedSpace(aPropValues,aPropNames,bLimitedHeight);
994 
995     const TickInfo* pPreviousVisibleTickInfo = nullptr;
996     sal_Int32 nTick = 0;
997     for( TickInfo* pTickInfo = rTickIter.firstInfo()
998         ; pTickInfo
999         ; pTickInfo = rTickIter.nextInfo(), nTick++ )
1000     {
1001         const TickInfo* pLastVisibleNeighbourTickInfo = pPreviousVisibleTickInfo;
1002 
1003         //don't create labels which does not fit into the rhythm
1004         if( nTick%rAxisLabelProperties.m_nRhythm != 0 )
1005             continue;
1006 
1007         //don't create labels for invisible ticks
1008         if( !pTickInfo->bPaintIt )
1009             continue;
1010 
1011         if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
1012         {
1013             // Overlapping is not allowed.  If the label overlaps with its
1014             // neighboring label, try increasing the tick interval (or rhythm
1015             // as it's called) and start over.
1016 
1017             if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
1018                        , rAxisLabelProperties.m_fRotationAngleDegree
1019                        , pTickInfo->aTickScreenPosition ) )
1020             {
1021                 // This tick overlaps with its neighbor. Increment the visible
1022                 // tick intervals (if that's allowed) and start over.
1023 
1024                 rAxisLabelProperties.m_nRhythm++;
1025                 removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
1026                 return false;
1027             }
1028         }
1029 
1030         bool bHasExtraColor=false;
1031         Color nExtraColor;
1032 
1033         OUString aLabel = getTextLabelString(
1034             aFixedNumberFormatter, pCategories, pTickInfo, isComplexCategoryAxis(),
1035             nExtraColor, bHasExtraColor);
1036 
1037         if(pColorAny)
1038             *pColorAny <<= bHasExtraColor?nExtraColor:nColor;
1039         if(pLimitedSpaceAny)
1040             *pLimitedSpaceAny <<= sal_Int32(-1*pTickInfo->nFactorForLimitedTextWidth);
1041 
1042         B2DVector aTickScreenPos2D = pTickInfo->aTickScreenPosition;
1043         aTickScreenPos2D += aTextToTickDistance;
1044         awt::Point aAnchorScreenPosition2D(
1045             static_cast<sal_Int32>(aTickScreenPos2D.getX())
1046             ,static_cast<sal_Int32>(aTickScreenPos2D.getY()));
1047 
1048         //create single label
1049         if(!pTickInfo->xTextShape.is())
1050             pTickInfo->xTextShape = createSingleLabel( xTarget
1051                                     , aAnchorScreenPosition2D, aLabel
1052                                     , rAxisLabelProperties, m_aAxisProperties
1053                                     , aPropNames, aPropValues, bIsHorizontalAxis );
1054         if(!pTickInfo->xTextShape.is())
1055             continue;
1056 
1057         recordMaximumTextSize( *pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree );
1058 
1059         //if NO OVERLAP -> remove overlapping shapes
1060         if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
1061         {
1062             // Check if the label still overlaps with its neighbor.
1063             if( doesOverlap( pLastVisibleNeighbourTickInfo->xTextShape, pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree ) )
1064             {
1065                 // It overlaps.
1066                 if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) )
1067                 {
1068                     // Try auto-rotating the labels at 45 degrees and
1069                     // start over.  This rotation angle will be stored for
1070                     // all future text shape creation runs.
1071                     // The nRhythm parameter is reset to 1 since the layout
1072                     // used for text labels is changed.
1073                     rAxisLabelProperties.autoRotate45();
1074                     m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree; // Store it for future runs.
1075                     removeTextShapesFromTicks();
1076                     rAxisLabelProperties.m_nRhythm = 1;
1077                     return false;
1078                 }
1079 
1080                 // Try incrementing the tick interval and start over.
1081                 rAxisLabelProperties.m_nRhythm++;
1082                 removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
1083                 return false;
1084             }
1085         }
1086 
1087         pPreviousVisibleTickInfo = pTickInfo;
1088     }
1089     return true;
1090 }
1091 
getAxisIntersectionValue() const1092 double VCartesianAxis::getAxisIntersectionValue() const
1093 {
1094     if (m_aAxisProperties.m_pfMainLinePositionAtOtherAxis)
1095         return *m_aAxisProperties.m_pfMainLinePositionAtOtherAxis;
1096 
1097     double fMin = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMinY();
1098     double fMax = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMaxY();
1099 
1100     return (m_aAxisProperties.m_eCrossoverType == css::chart::ChartAxisPosition_END) ? fMax : fMin;
1101 }
1102 
getLabelLineIntersectionValue() const1103 double VCartesianAxis::getLabelLineIntersectionValue() const
1104 {
1105     if (m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START)
1106         return (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMinY();
1107 
1108     if (m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END)
1109         return (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMaxY();
1110 
1111     return getAxisIntersectionValue();
1112 }
1113 
getExtraLineIntersectionValue() const1114 double VCartesianAxis::getExtraLineIntersectionValue() const
1115 {
1116     if( !m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis )
1117         return std::numeric_limits<double>::quiet_NaN();
1118 
1119     double fMin = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMinY();
1120     double fMax = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMaxY();
1121 
1122     if( *m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis <= fMin
1123         || *m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis >= fMax )
1124         return std::numeric_limits<double>::quiet_NaN();
1125 
1126     return *m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis;
1127 }
1128 
getScreenPosition(double fLogicX,double fLogicY,double fLogicZ) const1129 B2DVector VCartesianAxis::getScreenPosition( double fLogicX, double fLogicY, double fLogicZ ) const
1130 {
1131     B2DVector aRet(0,0);
1132 
1133     if( m_pPosHelper )
1134     {
1135         drawing::Position3D aScenePos = m_pPosHelper->transformLogicToScene( fLogicX, fLogicY, fLogicZ, true );
1136         if(m_nDimension==3)
1137         {
1138             if (m_xLogicTarget.is())
1139             {
1140                 tPropertyNameMap aDummyPropertyNameMap;
1141                 rtl::Reference<Svx3DExtrudeObject> xShape3DAnchor = ShapeFactory::createCube( m_xLogicTarget
1142                         , aScenePos,drawing::Direction3D(1,1,1), 0, nullptr, aDummyPropertyNameMap);
1143                 awt::Point a2DPos = xShape3DAnchor->getPosition(); //get 2D position from xShape3DAnchor
1144                 m_xLogicTarget->remove(xShape3DAnchor);
1145                 aRet.setX( a2DPos.X );
1146                 aRet.setY( a2DPos.Y );
1147             }
1148             else
1149             {
1150                 OSL_FAIL("cannot calculate screen position in VCartesianAxis::getScreenPosition");
1151             }
1152         }
1153         else
1154         {
1155             aRet.setX( aScenePos.PositionX );
1156             aRet.setY( aScenePos.PositionY );
1157         }
1158     }
1159 
1160     return aRet;
1161 }
1162 
getScreenPosAndLogicPos(double fLogicX_,double fLogicY_,double fLogicZ_) const1163 VCartesianAxis::ScreenPosAndLogicPos VCartesianAxis::getScreenPosAndLogicPos( double fLogicX_, double fLogicY_, double fLogicZ_ ) const
1164 {
1165     ScreenPosAndLogicPos aRet;
1166     aRet.fLogicX = fLogicX_;
1167     aRet.fLogicY = fLogicY_;
1168     aRet.fLogicZ = fLogicZ_;
1169     aRet.aScreenPos = getScreenPosition( fLogicX_, fLogicY_, fLogicZ_ );
1170     return aRet;
1171 }
1172 
1173 typedef std::vector< VCartesianAxis::ScreenPosAndLogicPos > tScreenPosAndLogicPosList;
1174 
1175 namespace {
1176 
1177 struct lcl_LessXPos
1178 {
operator ()chart::__anon68bc43380411::lcl_LessXPos1179     bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos& rPos1, const VCartesianAxis::ScreenPosAndLogicPos& rPos2 )
1180     {
1181         return ( rPos1.aScreenPos.getX() < rPos2.aScreenPos.getX() );
1182     }
1183 };
1184 
1185 struct lcl_GreaterYPos
1186 {
operator ()chart::__anon68bc43380411::lcl_GreaterYPos1187     bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos& rPos1, const VCartesianAxis::ScreenPosAndLogicPos& rPos2 )
1188     {
1189         return ( rPos1.aScreenPos.getY() > rPos2.aScreenPos.getY() );
1190     }
1191 };
1192 
1193 }
1194 
get2DAxisMainLine(B2DVector & rStart,B2DVector & rEnd,AxisLabelAlignment & rAlignment,double fCrossesOtherAxis) const1195 void VCartesianAxis::get2DAxisMainLine(
1196     B2DVector& rStart, B2DVector& rEnd, AxisLabelAlignment& rAlignment, double fCrossesOtherAxis ) const
1197 {
1198     //m_aAxisProperties might get updated and changed here because
1199     //    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 
1201     double const fMinX = m_pPosHelper->getLogicMinX();
1202     double const fMinY = m_pPosHelper->getLogicMinY();
1203     double const fMinZ = m_pPosHelper->getLogicMinZ();
1204     double const fMaxX = m_pPosHelper->getLogicMaxX();
1205     double const fMaxY = m_pPosHelper->getLogicMaxY();
1206     double const fMaxZ = m_pPosHelper->getLogicMaxZ();
1207 
1208     double fXOnXPlane = fMinX;
1209     double fXOther = fMaxX;
1210     int nDifferentValue = !m_pPosHelper->isMathematicalOrientationX() ? -1 : 1;
1211     if( !m_pPosHelper->isSwapXAndY() )
1212         nDifferentValue *= (m_eLeftWallPos != CuboidPlanePosition_Left) ? -1 : 1;
1213     else
1214         nDifferentValue *= (m_eBottomPos != CuboidPlanePosition_Bottom) ? -1 : 1;
1215     if( nDifferentValue<0 )
1216     {
1217         fXOnXPlane = fMaxX;
1218         fXOther = fMinX;
1219     }
1220 
1221     double fYOnYPlane = fMinY;
1222     double fYOther = fMaxY;
1223     nDifferentValue = !m_pPosHelper->isMathematicalOrientationY() ? -1 : 1;
1224     if( !m_pPosHelper->isSwapXAndY() )
1225         nDifferentValue *= (m_eBottomPos != CuboidPlanePosition_Bottom) ? -1 : 1;
1226     else
1227         nDifferentValue *= (m_eLeftWallPos != CuboidPlanePosition_Left) ? -1 : 1;
1228     if( nDifferentValue<0 )
1229     {
1230         fYOnYPlane = fMaxY;
1231         fYOther = fMinY;
1232     }
1233 
1234     double fZOnZPlane = fMaxZ;
1235     double fZOther = fMinZ;
1236     nDifferentValue = !m_pPosHelper->isMathematicalOrientationZ() ? -1 : 1;
1237     nDifferentValue *= (m_eBackWallPos != CuboidPlanePosition_Back) ? -1 : 1;
1238     if( nDifferentValue<0 )
1239     {
1240         fZOnZPlane = fMinZ;
1241         fZOther = fMaxZ;
1242     }
1243 
1244     double fXStart = fMinX;
1245     double fYStart = fMinY;
1246     double fZStart = fMinZ;
1247     double fXEnd;
1248     double fYEnd;
1249     double fZEnd = fZStart;
1250 
1251     if( m_nDimensionIndex==0 ) //x-axis
1252     {
1253         if( fCrossesOtherAxis < fMinY )
1254             fCrossesOtherAxis = fMinY;
1255         else if( fCrossesOtherAxis > fMaxY )
1256             fCrossesOtherAxis = fMaxY;
1257 
1258         fYStart = fYEnd = fCrossesOtherAxis;
1259         fXEnd=m_pPosHelper->getLogicMaxX();
1260 
1261         if(m_nDimension==3)
1262         {
1263             if( AxisHelper::isAxisPositioningEnabled() )
1264             {
1265                 if( ::rtl::math::approxEqual( fYOther, fYStart) )
1266                     fZStart = fZEnd = fZOnZPlane;
1267                 else
1268                     fZStart = fZEnd = fZOther;
1269             }
1270             else
1271             {
1272                 rStart = getScreenPosition( fXStart, fYStart, fZStart );
1273                 rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
1274 
1275                 double fDeltaX = rEnd.getX() - rStart.getX();
1276                 double fDeltaY = rEnd.getY() - rStart.getY();
1277 
1278                 //only those points are candidates which are lying on exactly one wall as these are outer edges
1279                 tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fMinX, fYOnYPlane, fZOther ), getScreenPosAndLogicPos( fMinX, fYOther, fZOnZPlane ) };
1280 
1281                 if( fabs(fDeltaY) > fabs(fDeltaX)  )
1282                 {
1283                     rAlignment.meAlignment = LABEL_ALIGN_LEFT;
1284                     //choose most left positions
1285                     std::sort( aPosList.begin(), aPosList.end(), lcl_LessXPos() );
1286                     rAlignment.mfLabelDirection = (fDeltaY < 0) ? -1.0 : 1.0;
1287                 }
1288                 else
1289                 {
1290                     rAlignment.meAlignment = LABEL_ALIGN_BOTTOM;
1291                     //choose most bottom positions
1292                     std::sort( aPosList.begin(), aPosList.end(), lcl_GreaterYPos() );
1293                     rAlignment.mfLabelDirection = (fDeltaX < 0) ? -1.0 : 1.0;
1294                 }
1295                 ScreenPosAndLogicPos aBestPos( aPosList[0] );
1296                 fYStart = fYEnd = aBestPos.fLogicY;
1297                 fZStart = fZEnd = aBestPos.fLogicZ;
1298                 if( !m_pPosHelper->isMathematicalOrientationX() )
1299                     rAlignment.mfLabelDirection *= -1.0;
1300             }
1301         }//end 3D x axis
1302     }
1303     else if( m_nDimensionIndex==1 ) //y-axis
1304     {
1305         if( fCrossesOtherAxis < fMinX )
1306             fCrossesOtherAxis = fMinX;
1307         else if( fCrossesOtherAxis > fMaxX )
1308             fCrossesOtherAxis = fMaxX;
1309 
1310         fXStart = fXEnd = fCrossesOtherAxis;
1311         fYEnd=m_pPosHelper->getLogicMaxY();
1312 
1313         if(m_nDimension==3)
1314         {
1315             if( AxisHelper::isAxisPositioningEnabled() )
1316             {
1317                 if( ::rtl::math::approxEqual( fXOther, fXStart) )
1318                     fZStart = fZEnd = fZOnZPlane;
1319                 else
1320                     fZStart = fZEnd = fZOther;
1321             }
1322             else
1323             {
1324                 rStart = getScreenPosition( fXStart, fYStart, fZStart );
1325                 rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
1326 
1327                 double fDeltaX = rEnd.getX() - rStart.getX();
1328                 double fDeltaY = rEnd.getY() - rStart.getY();
1329 
1330                 //only those points are candidates which are lying on exactly one wall as these are outer edges
1331                 tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fXOnXPlane, fMinY, fZOther ), getScreenPosAndLogicPos( fXOther, fMinY, fZOnZPlane ) };
1332 
1333                 if( fabs(fDeltaY) > fabs(fDeltaX)  )
1334                 {
1335                     rAlignment.meAlignment = LABEL_ALIGN_LEFT;
1336                     //choose most left positions
1337                     std::sort( aPosList.begin(), aPosList.end(), lcl_LessXPos() );
1338                     rAlignment.mfLabelDirection = (fDeltaY < 0) ? -1.0 : 1.0;
1339                 }
1340                 else
1341                 {
1342                     rAlignment.meAlignment = LABEL_ALIGN_BOTTOM;
1343                     //choose most bottom positions
1344                     std::sort( aPosList.begin(), aPosList.end(), lcl_GreaterYPos() );
1345                     rAlignment.mfLabelDirection = (fDeltaX < 0) ? -1.0 : 1.0;
1346                 }
1347                 ScreenPosAndLogicPos aBestPos( aPosList[0] );
1348                 fXStart = fXEnd = aBestPos.fLogicX;
1349                 fZStart = fZEnd = aBestPos.fLogicZ;
1350                 if( !m_pPosHelper->isMathematicalOrientationY() )
1351                     rAlignment.mfLabelDirection *= -1.0;
1352             }
1353         }//end 3D y axis
1354     }
1355     else //z-axis
1356     {
1357         fZEnd = m_pPosHelper->getLogicMaxZ();
1358         if( AxisHelper::isAxisPositioningEnabled() )
1359         {
1360             if( !m_aAxisProperties.m_bSwapXAndY )
1361             {
1362                 if( fCrossesOtherAxis < fMinY )
1363                     fCrossesOtherAxis = fMinY;
1364                 else if( fCrossesOtherAxis > fMaxY )
1365                     fCrossesOtherAxis = fMaxY;
1366                 fYStart = fYEnd = fCrossesOtherAxis;
1367 
1368                 if( ::rtl::math::approxEqual( fYOther, fYStart) )
1369                     fXStart = fXEnd = fXOnXPlane;
1370                 else
1371                     fXStart = fXEnd = fXOther;
1372             }
1373             else
1374             {
1375                 if( fCrossesOtherAxis < fMinX )
1376                     fCrossesOtherAxis = fMinX;
1377                 else if( fCrossesOtherAxis > fMaxX )
1378                     fCrossesOtherAxis = fMaxX;
1379                 fXStart = fXEnd = fCrossesOtherAxis;
1380 
1381                 if( ::rtl::math::approxEqual( fXOther, fXStart) )
1382                     fYStart = fYEnd = fYOnYPlane;
1383                 else
1384                     fYStart = fYEnd = fYOther;
1385             }
1386         }
1387         else
1388         {
1389             if( !m_pPosHelper->isSwapXAndY() )
1390             {
1391                 fXStart = fXEnd = m_pPosHelper->isMathematicalOrientationX() ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMinX();
1392                 fYStart = fYEnd = m_pPosHelper->isMathematicalOrientationY() ? m_pPosHelper->getLogicMinY() : m_pPosHelper->getLogicMaxY();
1393             }
1394             else
1395             {
1396                 fXStart = fXEnd = m_pPosHelper->isMathematicalOrientationX() ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMaxX();
1397                 fYStart = fYEnd = m_pPosHelper->isMathematicalOrientationY() ? m_pPosHelper->getLogicMaxY() : m_pPosHelper->getLogicMinY();
1398             }
1399 
1400             if(m_nDimension==3)
1401             {
1402                 rStart = getScreenPosition( fXStart, fYStart, fZStart );
1403                 rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
1404 
1405                 double fDeltaX = rEnd.getX() - rStart.getX();
1406 
1407                 //only those points are candidates which are lying on exactly one wall as these are outer edges
1408                 tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fXOther, fYOnYPlane, fMinZ ), getScreenPosAndLogicPos( fXOnXPlane, fYOther, fMinZ ) };
1409 
1410                 std::sort( aPosList.begin(), aPosList.end(), lcl_GreaterYPos() );
1411                 ScreenPosAndLogicPos aBestPos( aPosList[0] );
1412                 ScreenPosAndLogicPos aNotSoGoodPos( aPosList[1] );
1413 
1414                 //choose most bottom positions
1415                 if( fDeltaX != 0.0 ) // prefer left-right alignments
1416                 {
1417                     if( aBestPos.aScreenPos.getX() > aNotSoGoodPos.aScreenPos.getX() )
1418                         rAlignment.meAlignment = LABEL_ALIGN_RIGHT;
1419                     else
1420                          rAlignment.meAlignment = LABEL_ALIGN_LEFT;
1421                 }
1422                 else
1423                 {
1424                     if( aBestPos.aScreenPos.getY() > aNotSoGoodPos.aScreenPos.getY() )
1425                         rAlignment.meAlignment = LABEL_ALIGN_BOTTOM;
1426                     else
1427                         rAlignment.meAlignment = LABEL_ALIGN_TOP;
1428                 }
1429 
1430                 rAlignment.mfLabelDirection = (fDeltaX < 0) ? -1.0 : 1.0;
1431                 if( !m_pPosHelper->isMathematicalOrientationZ() )
1432                     rAlignment.mfLabelDirection *= -1.0;
1433 
1434                 fXStart = fXEnd = aBestPos.fLogicX;
1435                 fYStart = fYEnd = aBestPos.fLogicY;
1436             }
1437         }//end 3D z axis
1438     }
1439 
1440     rStart = getScreenPosition( fXStart, fYStart, fZStart );
1441     rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
1442 
1443     if(m_nDimension==3 && !AxisHelper::isAxisPositioningEnabled() )
1444         rAlignment.mfInnerTickDirection = rAlignment.mfLabelDirection;//to behave like before
1445 
1446     if(!(m_nDimension==3 && AxisHelper::isAxisPositioningEnabled()) )
1447         return;
1448 
1449     double fDeltaX = rEnd.getX() - rStart.getX();
1450     double fDeltaY = rEnd.getY() - rStart.getY();
1451 
1452     if( m_nDimensionIndex==2 )
1453     {
1454         if( m_eLeftWallPos != CuboidPlanePosition_Left )
1455         {
1456             rAlignment.mfLabelDirection *= -1.0;
1457             rAlignment.mfInnerTickDirection *= -1.0;
1458         }
1459 
1460         rAlignment.meAlignment =
1461             (rAlignment.mfLabelDirection < 0) ?
1462                 LABEL_ALIGN_LEFT :  LABEL_ALIGN_RIGHT;
1463 
1464         if( ( fDeltaY<0 && m_aScale.Orientation == chart2::AxisOrientation_REVERSE ) ||
1465             ( fDeltaY>0 && m_aScale.Orientation == chart2::AxisOrientation_MATHEMATICAL ) )
1466             rAlignment.meAlignment =
1467                 (rAlignment.meAlignment == LABEL_ALIGN_RIGHT) ?
1468                     LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
1469     }
1470     else if( fabs(fDeltaY) > fabs(fDeltaX) )
1471     {
1472         if( m_eBackWallPos != CuboidPlanePosition_Back )
1473         {
1474             rAlignment.mfLabelDirection *= -1.0;
1475             rAlignment.mfInnerTickDirection *= -1.0;
1476         }
1477 
1478         rAlignment.meAlignment =
1479             (rAlignment.mfLabelDirection < 0) ?
1480                 LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
1481 
1482         if( ( fDeltaY<0 && m_aScale.Orientation == chart2::AxisOrientation_REVERSE ) ||
1483             ( fDeltaY>0 && m_aScale.Orientation == chart2::AxisOrientation_MATHEMATICAL ) )
1484             rAlignment.meAlignment =
1485                 (rAlignment.meAlignment == LABEL_ALIGN_RIGHT) ?
1486                     LABEL_ALIGN_LEFT :  LABEL_ALIGN_RIGHT;
1487     }
1488     else
1489     {
1490         if( m_eBackWallPos != CuboidPlanePosition_Back )
1491         {
1492             rAlignment.mfLabelDirection *= -1.0;
1493             rAlignment.mfInnerTickDirection *= -1.0;
1494         }
1495 
1496         rAlignment.meAlignment =
1497             (rAlignment.mfLabelDirection < 0) ?
1498                 LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
1499 
1500         if( ( fDeltaX>0 && m_aScale.Orientation == chart2::AxisOrientation_REVERSE ) ||
1501             ( fDeltaX<0 && m_aScale.Orientation == chart2::AxisOrientation_MATHEMATICAL ) )
1502             rAlignment.meAlignment =
1503                 (rAlignment.meAlignment == LABEL_ALIGN_TOP) ?
1504                     LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP;
1505     }
1506 }
1507 
createTickFactory()1508 TickFactory* VCartesianAxis::createTickFactory()
1509 {
1510     return createTickFactory2D();
1511 }
1512 
createTickFactory2D()1513 TickFactory2D* VCartesianAxis::createTickFactory2D()
1514 {
1515     AxisLabelAlignment aLabelAlign = m_aAxisProperties.maLabelAlignment;
1516     B2DVector aStart, aEnd;
1517     get2DAxisMainLine(aStart, aEnd, aLabelAlign, getAxisIntersectionValue());
1518 
1519     B2DVector aLabelLineStart, aLabelLineEnd;
1520     get2DAxisMainLine(aLabelLineStart, aLabelLineEnd, aLabelAlign, getLabelLineIntersectionValue());
1521     m_aAxisProperties.maLabelAlignment = aLabelAlign;
1522 
1523     return new TickFactory2D( m_aScale, m_aIncrement, aStart, aEnd, aLabelLineStart-aStart );
1524 }
1525 
lcl_hideIdenticalScreenValues(TickIter & rTickIter)1526 static void lcl_hideIdenticalScreenValues( TickIter& rTickIter )
1527 {
1528     TickInfo* pPrevTickInfo = rTickIter.firstInfo();
1529     if (!pPrevTickInfo)
1530         return;
1531 
1532     pPrevTickInfo->bPaintIt = true;
1533     for( TickInfo* pTickInfo = rTickIter.nextInfo(); pTickInfo; pTickInfo = rTickIter.nextInfo())
1534     {
1535         pTickInfo->bPaintIt = (pTickInfo->aTickScreenPosition != pPrevTickInfo->aTickScreenPosition);
1536         pPrevTickInfo = pTickInfo;
1537     }
1538 }
1539 
1540 //'hide' tickmarks with identical screen values in aAllTickInfos
hideIdenticalScreenValues(TickInfoArraysType & rTickInfos) const1541 void VCartesianAxis::hideIdenticalScreenValues( TickInfoArraysType& rTickInfos ) const
1542 {
1543     if( isComplexCategoryAxis() || isDateAxis() )
1544     {
1545         sal_Int32 nCount = rTickInfos.size();
1546         for( sal_Int32 nN=0; nN<nCount; nN++ )
1547         {
1548             PureTickIter aTickIter( rTickInfos[nN] );
1549             lcl_hideIdenticalScreenValues( aTickIter );
1550         }
1551     }
1552     else
1553     {
1554         EquidistantTickIter aTickIter( rTickInfos, m_aIncrement, -1 );
1555         lcl_hideIdenticalScreenValues( aTickIter );
1556     }
1557 }
1558 
estimateMaximumAutoMainIncrementCount()1559 sal_Int32 VCartesianAxis::estimateMaximumAutoMainIncrementCount()
1560 {
1561     sal_Int32 nRet = 10;
1562 
1563     if( m_nMaximumTextWidthSoFar==0 && m_nMaximumTextHeightSoFar==0 )
1564         return nRet;
1565 
1566     B2DVector aStart, aEnd;
1567     AxisLabelAlignment aLabelAlign = m_aAxisProperties.maLabelAlignment;
1568     get2DAxisMainLine(aStart, aEnd, aLabelAlign, getAxisIntersectionValue());
1569     m_aAxisProperties.maLabelAlignment = aLabelAlign;
1570 
1571     sal_Int32 nMaxHeight = static_cast<sal_Int32>(fabs(aEnd.getY()-aStart.getY()));
1572     sal_Int32 nMaxWidth = static_cast<sal_Int32>(fabs(aEnd.getX()-aStart.getX()));
1573 
1574     sal_Int32 nTotalAvailable = nMaxHeight;
1575     sal_Int32 nSingleNeeded = m_nMaximumTextHeightSoFar;
1576     sal_Int32 nMaxSameLabel = 0;
1577 
1578     // tdf#48041: do not duplicate the value labels because of rounding
1579     if (m_aAxisProperties.m_nAxisType != css::chart2::AxisType::DATE)
1580     {
1581         FixedNumberFormatter aFixedNumberFormatterTest(m_xNumberFormatsSupplier, m_aAxisLabelProperties.m_nNumberFormatKey);
1582         OUString sPreviousValueLabel;
1583         sal_Int32 nSameLabel = 0;
1584         for (auto const & nLabel: m_aAllTickInfos[0])
1585         {
1586             Color nColor = COL_AUTO;
1587             bool bHasColor = false;
1588             OUString sValueLabel = aFixedNumberFormatterTest.getFormattedString(nLabel.fScaledTickValue, nColor, bHasColor);
1589             if (sValueLabel == sPreviousValueLabel)
1590             {
1591                 nSameLabel++;
1592                 if (nSameLabel > nMaxSameLabel)
1593                     nMaxSameLabel = nSameLabel;
1594             }
1595             else
1596                 nSameLabel = 0;
1597             sPreviousValueLabel = sValueLabel;
1598         }
1599     }
1600     //for horizontal axis:
1601     if( (m_nDimensionIndex == 0 && !m_aAxisProperties.m_bSwapXAndY)
1602         || (m_nDimensionIndex == 1 && m_aAxisProperties.m_bSwapXAndY) )
1603     {
1604         nTotalAvailable = nMaxWidth;
1605         nSingleNeeded = m_nMaximumTextWidthSoFar;
1606     }
1607 
1608     if( nSingleNeeded>0 )
1609         nRet = nTotalAvailable/nSingleNeeded;
1610 
1611     if ( nMaxSameLabel > 0 )
1612     {
1613         sal_Int32 nRetNoSameLabel = m_aAllTickInfos[0].size() / (nMaxSameLabel + 1);
1614         if ( nRet > nRetNoSameLabel )
1615            nRet = nRetNoSameLabel;
1616     }
1617 
1618     return nRet;
1619 }
1620 
doStaggeringOfLabels(const AxisLabelProperties & rAxisLabelProperties,TickFactory2D const * pTickFactory2D)1621 void VCartesianAxis::doStaggeringOfLabels( const AxisLabelProperties& rAxisLabelProperties, TickFactory2D const * pTickFactory2D )
1622 {
1623     if( !pTickFactory2D )
1624         return;
1625 
1626     if( isComplexCategoryAxis() )
1627     {
1628         sal_Int32 nTextLevelCount = getTextLevelCount();
1629         B2DVector aCumulatedLabelsDistance(0,0);
1630         for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
1631         {
1632             std::unique_ptr<TickIter> apTickIter(createLabelTickIterator(nTextLevel));
1633             if (apTickIter)
1634             {
1635                 double fRotationAngleDegree = m_aAxisLabelProperties.m_fRotationAngleDegree;
1636                 if( nTextLevel>0 )
1637                 {
1638                     lcl_shiftLabels(*apTickIter, aCumulatedLabelsDistance);
1639                     //multilevel labels: 0 or 90 by default
1640                     if( m_aAxisProperties.m_bSwapXAndY )
1641                         fRotationAngleDegree = 90.0;
1642                     else
1643                         fRotationAngleDegree = 0.0;
1644                 }
1645                 aCumulatedLabelsDistance += lcl_getLabelsDistance(
1646                     *apTickIter, pTickFactory2D->getDistanceAxisTickToText(m_aAxisProperties),
1647                     fRotationAngleDegree);
1648             }
1649         }
1650     }
1651     else if (rAxisLabelProperties.isStaggered())
1652     {
1653         if( !m_aAllTickInfos.empty() )
1654         {
1655             LabelIterator aInnerIter( m_aAllTickInfos[0], rAxisLabelProperties.m_eStaggering, true );
1656             LabelIterator aOuterIter( m_aAllTickInfos[0], rAxisLabelProperties.m_eStaggering, false );
1657 
1658             lcl_shiftLabels( aOuterIter
1659                 , lcl_getLabelsDistance( aInnerIter
1660                     , pTickFactory2D->getDistanceAxisTickToText( m_aAxisProperties ), 0.0 ) );
1661         }
1662     }
1663 }
1664 
createDataTableShape(std::unique_ptr<TickFactory2D> const & rpTickFactory2D)1665 void VCartesianAxis::createDataTableShape(std::unique_ptr<TickFactory2D> const& rpTickFactory2D)
1666 {
1667     // Check if we can create the data table shape
1668     // Data table view and m_bDisplayDataTable must be true
1669     if (!m_pDataTableView || !m_aAxisProperties.m_bDisplayDataTable)
1670         return;
1671 
1672     m_pDataTableView->initializeShapes(m_xDataTableTarget);
1673     basegfx::B2DVector aStart = rpTickFactory2D->getXaxisStartPos();
1674     basegfx::B2DVector aEnd = rpTickFactory2D->getXaxisEndPos();
1675 
1676     rpTickFactory2D->updateScreenValues(m_aAllTickInfos);
1677 
1678     sal_Int32 nDistance = -1;
1679 
1680     std::unique_ptr<TickIter> apTickIter(createLabelTickIterator(0));
1681     if (apTickIter)
1682     {
1683         nDistance = TickFactory2D::getTickScreenDistance(*apTickIter);
1684         if (getTextLevelCount() > 1)
1685             nDistance *= 2;
1686     }
1687 
1688     if (nDistance <= 0)
1689     {
1690         // we only have one data series so we have no TickMarks, therefore calculate and use the table size
1691         auto rDelta = aEnd - aStart;
1692         nDistance = basegfx::fround(rDelta.getX());
1693     }
1694 
1695     if (nDistance > 0)
1696     {
1697         m_pDataTableView->createShapes(aStart, aEnd, nDistance);
1698     }
1699 }
1700 
createLabels()1701 void VCartesianAxis::createLabels()
1702 {
1703     if( !prepareShapeCreation() )
1704         return;
1705 
1706     std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
1707 
1708     createDataTableShape(apTickFactory2D);
1709 
1710     //create labels
1711     if (!m_aAxisProperties.m_bDisplayLabels)
1712         return;
1713 
1714     TickFactory2D* pTickFactory2D = apTickFactory2D.get();
1715 
1716     //get the transformed screen values for all tickmarks in aAllTickInfos
1717     pTickFactory2D->updateScreenValues( m_aAllTickInfos );
1718     //'hide' tickmarks with identical screen values in aAllTickInfos
1719     hideIdenticalScreenValues( m_aAllTickInfos );
1720 
1721     removeTextShapesFromTicks();
1722 
1723     //create tick mark text shapes
1724     sal_Int32 nTextLevelCount = getTextLevelCount();
1725     sal_Int32 nScreenDistanceBetweenTicks = -1;
1726     for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
1727     {
1728         std::unique_ptr< TickIter > apTickIter(createLabelTickIterator( nTextLevel ));
1729         if(apTickIter)
1730         {
1731             if(nTextLevel==0)
1732             {
1733                 nScreenDistanceBetweenTicks = TickFactory2D::getTickScreenDistance(*apTickIter);
1734                 if( nTextLevelCount>1 )
1735                     nScreenDistanceBetweenTicks*=2; //the above used tick iter does contain also the sub ticks -> thus the given distance is only the half
1736             }
1737 
1738             AxisLabelProperties aComplexProps(m_aAxisLabelProperties);
1739             if( m_aAxisProperties.m_bComplexCategories )
1740             {
1741                 aComplexProps.m_bLineBreakAllowed = true;
1742                 aComplexProps.m_bOverlapAllowed = aComplexProps.m_fRotationAngleDegree != 0.0;
1743                 if( nTextLevel > 0 )
1744                 {
1745                     //multilevel labels: 0 or 90 by default
1746                     if( m_aAxisProperties.m_bSwapXAndY )
1747                         aComplexProps.m_fRotationAngleDegree = 90.0;
1748                     else
1749                         aComplexProps.m_fRotationAngleDegree = 0.0;
1750                 }
1751             }
1752             AxisLabelProperties& rAxisLabelProperties =  m_aAxisProperties.m_bComplexCategories ? aComplexProps : m_aAxisLabelProperties;
1753             while (!createTextShapes(m_xTextTarget, *apTickIter, rAxisLabelProperties,
1754                                      pTickFactory2D, nScreenDistanceBetweenTicks))
1755             {
1756             };
1757         }
1758     }
1759     doStaggeringOfLabels( m_aAxisLabelProperties, pTickFactory2D );
1760 
1761     if (m_pDataTableView)
1762     {
1763         sal_Int32 x = m_xTextTarget->getPosition().X;
1764         sal_Int32 y = m_xTextTarget->getPosition().Y;
1765         sal_Int32 height = m_xTextTarget->getSize().Height;
1766         m_pDataTableView->changePosition(x, y + height);
1767     }
1768 }
1769 
createMaximumLabels()1770 void VCartesianAxis::createMaximumLabels()
1771 {
1772     m_bRecordMaximumTextSize = true;
1773     const comphelper::ScopeGuard aGuard([this]() { m_bRecordMaximumTextSize = false; });
1774 
1775     if( !prepareShapeCreation() )
1776         return;
1777 
1778     //create labels
1779     if (!m_aAxisProperties.m_bDisplayLabels)
1780         return;
1781 
1782     std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
1783     TickFactory2D* pTickFactory2D = apTickFactory2D.get();
1784 
1785     //get the transformed screen values for all tickmarks in aAllTickInfos
1786     pTickFactory2D->updateScreenValues( m_aAllTickInfos );
1787 
1788     //create tick mark text shapes
1789     //@todo: iterate through all tick depth which should be labeled
1790 
1791     AxisLabelProperties aAxisLabelProperties( m_aAxisLabelProperties );
1792     if( isAutoStaggeringOfLabelsAllowed( aAxisLabelProperties, pTickFactory2D->isHorizontalAxis(), pTickFactory2D->isVerticalAxis() ) )
1793         aAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
1794 
1795     aAxisLabelProperties.m_bOverlapAllowed = true;
1796     aAxisLabelProperties.m_bLineBreakAllowed = false;
1797     sal_Int32 nTextLevelCount = getTextLevelCount();
1798     for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
1799     {
1800         std::unique_ptr< TickIter > apTickIter(createMaximumLabelTickIterator( nTextLevel ));
1801         if(apTickIter)
1802         {
1803             while (!createTextShapes(m_xTextTarget, *apTickIter, aAxisLabelProperties,
1804                                      pTickFactory2D, -1))
1805             {
1806             };
1807         }
1808     }
1809     doStaggeringOfLabels( aAxisLabelProperties, pTickFactory2D );
1810 }
1811 
updatePositions()1812 void VCartesianAxis::updatePositions()
1813 {
1814     //update positions of labels
1815     if (!m_aAxisProperties.m_bDisplayLabels)
1816         return;
1817 
1818     std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
1819     TickFactory2D* pTickFactory2D = apTickFactory2D.get();
1820 
1821     //update positions of all existing text shapes
1822     pTickFactory2D->updateScreenValues( m_aAllTickInfos );
1823 
1824     sal_Int32 nDepth=0;
1825     for (auto const& tickInfos : m_aAllTickInfos)
1826     {
1827         for (auto const& tickInfo : tickInfos)
1828         {
1829             const rtl::Reference<SvxShapeText> & xShape2DText(tickInfo.xTextShape);
1830             if( xShape2DText.is() )
1831             {
1832                 B2DVector aTextToTickDistance( pTickFactory2D->getDistanceAxisTickToText( m_aAxisProperties, true ) );
1833                 B2DVector aTickScreenPos2D(tickInfo.aTickScreenPosition);
1834                 aTickScreenPos2D += aTextToTickDistance;
1835                 awt::Point aAnchorScreenPosition2D(
1836                     static_cast<sal_Int32>(aTickScreenPos2D.getX())
1837                     ,static_cast<sal_Int32>(aTickScreenPos2D.getY()));
1838 
1839                 double fRotationAngleDegree = m_aAxisLabelProperties.m_fRotationAngleDegree;
1840                 if( nDepth > 0 )
1841                 {
1842                     //multilevel labels: 0 or 90 by default
1843                     if( pTickFactory2D->isHorizontalAxis() )
1844                         fRotationAngleDegree = 0.0;
1845                     else
1846                         fRotationAngleDegree = 90;
1847                 }
1848 
1849                 // #i78696# use mathematically correct rotation now
1850                 const double fRotationAnglePi(-basegfx::deg2rad(fRotationAngleDegree));
1851                 uno::Any aATransformation = ShapeFactory::makeTransformation(aAnchorScreenPosition2D, fRotationAnglePi);
1852 
1853                 //set new position
1854                 try
1855                 {
1856                     xShape2DText->SvxShape::setPropertyValue( u"Transformation"_ustr, aATransformation );
1857                 }
1858                 catch( const uno::Exception& )
1859                 {
1860                     TOOLS_WARN_EXCEPTION("chart2", "" );
1861                 }
1862 
1863                 //correctPositionForRotation
1864                 LabelPositionHelper::correctPositionForRotation( xShape2DText
1865                     , m_aAxisProperties.maLabelAlignment.meAlignment, fRotationAngleDegree, m_aAxisProperties.m_bComplexCategories );
1866             }
1867         }
1868         ++nDepth;
1869     }
1870 
1871     doStaggeringOfLabels( m_aAxisLabelProperties, pTickFactory2D );
1872 }
1873 
createTickMarkLineShapes(TickInfoArrayType & rTickInfos,const TickmarkProperties & rTickmarkProperties,TickFactory2D const & rTickFactory2D,bool bOnlyAtLabels)1874 void VCartesianAxis::createTickMarkLineShapes( TickInfoArrayType& rTickInfos, const TickmarkProperties& rTickmarkProperties, TickFactory2D const & rTickFactory2D, bool bOnlyAtLabels )
1875 {
1876     sal_Int32 nPointCount = rTickInfos.size();
1877     drawing::PointSequenceSequence aPoints(2*nPointCount);
1878 
1879     sal_Int32 nN = 0;
1880     for (auto const& tickInfo : rTickInfos)
1881     {
1882         if( !tickInfo.bPaintIt )
1883             continue;
1884 
1885         bool bTicksAtLabels = ( m_aAxisProperties.m_eTickmarkPos != css::chart::ChartAxisMarkPosition_AT_AXIS );
1886         double fInnerDirectionSign = m_aAxisProperties.maLabelAlignment.mfInnerTickDirection;
1887         if( bTicksAtLabels && m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END )
1888             fInnerDirectionSign *= -1.0;
1889         bTicksAtLabels = bTicksAtLabels || bOnlyAtLabels;
1890         //add ticks at labels:
1891         rTickFactory2D.addPointSequenceForTickLine( aPoints, nN++, tickInfo.fScaledTickValue
1892             , fInnerDirectionSign , rTickmarkProperties, bTicksAtLabels );
1893         //add ticks at axis (without labels):
1894         if( !bOnlyAtLabels && m_aAxisProperties.m_eTickmarkPos == css::chart::ChartAxisMarkPosition_AT_LABELS_AND_AXIS )
1895             rTickFactory2D.addPointSequenceForTickLine( aPoints, nN++, tickInfo.fScaledTickValue
1896                 , m_aAxisProperties.maLabelAlignment.mfInnerTickDirection, rTickmarkProperties, !bTicksAtLabels );
1897     }
1898     aPoints.realloc(nN);
1899     ShapeFactory::createLine2D( m_xGroupShape_Shapes, aPoints
1900                                 , &rTickmarkProperties.aLineProperties );
1901 }
1902 
createShapes()1903 void VCartesianAxis::createShapes()
1904 {
1905     if( !prepareShapeCreation() )
1906         return;
1907 
1908     //create line shapes
1909     if(m_nDimension==2)
1910     {
1911         std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
1912         TickFactory2D* pTickFactory2D = apTickFactory2D.get();
1913 
1914         //create extra long ticks to separate complex categories (create them only there where the labels are)
1915         if( isComplexCategoryAxis() )
1916         {
1917             TickInfoArraysType aComplexTickInfos;
1918             createAllTickInfosFromComplexCategories( aComplexTickInfos, true );
1919             pTickFactory2D->updateScreenValues( aComplexTickInfos );
1920             hideIdenticalScreenValues( aComplexTickInfos );
1921 
1922             std::vector<TickmarkProperties> aTickmarkPropertiesList;
1923             static const bool bIncludeSpaceBetweenTickAndText = false;
1924             sal_Int32 nOffset = static_cast<sal_Int32>(pTickFactory2D->getDistanceAxisTickToText( m_aAxisProperties, false, bIncludeSpaceBetweenTickAndText ).getLength());
1925             sal_Int32 nTextLevelCount = getTextLevelCount();
1926             for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
1927             {
1928                 std::unique_ptr< TickIter > apTickIter(createLabelTickIterator( nTextLevel ));
1929                 if( apTickIter )
1930                 {
1931                     double fRotationAngleDegree = m_aAxisLabelProperties.m_fRotationAngleDegree;
1932                     if( nTextLevel > 0 )
1933                     {
1934                         //Multi-level Labels: default to 0 or 90
1935                         if( m_aAxisProperties.m_bSwapXAndY )
1936                             fRotationAngleDegree = 90.0;
1937                         else
1938                             fRotationAngleDegree = 0.0;
1939                     }
1940                     B2DVector aLabelsDistance(lcl_getLabelsDistance(
1941                         *apTickIter, pTickFactory2D->getDistanceAxisTickToText(m_aAxisProperties),
1942                         fRotationAngleDegree));
1943                     sal_Int32 nCurrentLength = static_cast<sal_Int32>(aLabelsDistance.getLength());
1944                     aTickmarkPropertiesList.push_back( m_aAxisProperties.makeTickmarkPropertiesForComplexCategories( nOffset + nCurrentLength, 0 ) );
1945                     nOffset += nCurrentLength;
1946                 }
1947             }
1948 
1949             sal_Int32 nTickmarkPropertiesCount = aTickmarkPropertiesList.size();
1950             TickInfoArraysType::iterator aDepthIter             = aComplexTickInfos.begin();
1951             const TickInfoArraysType::const_iterator aDepthEnd  = aComplexTickInfos.end();
1952             for( sal_Int32 nDepth=0; aDepthIter != aDepthEnd && nDepth < nTickmarkPropertiesCount; ++aDepthIter, nDepth++ )
1953             {
1954                 if(nDepth==0 && !m_aAxisProperties.m_nMajorTickmarks)
1955                     continue;
1956                 createTickMarkLineShapes( *aDepthIter, aTickmarkPropertiesList[nDepth], *pTickFactory2D, true /*bOnlyAtLabels*/ );
1957             }
1958         }
1959         //create normal ticks for major and minor intervals
1960         {
1961             TickInfoArraysType aUnshiftedTickInfos;
1962             if( m_aScale.m_bShiftedCategoryPosition )// if m_bShiftedCategoryPosition==true the tickmarks in m_aAllTickInfos are shifted
1963             {
1964                 pTickFactory2D->getAllTicks( aUnshiftedTickInfos );
1965                 pTickFactory2D->updateScreenValues( aUnshiftedTickInfos );
1966                 hideIdenticalScreenValues( aUnshiftedTickInfos );
1967             }
1968             TickInfoArraysType& rAllTickInfos = m_aScale.m_bShiftedCategoryPosition ? aUnshiftedTickInfos : m_aAllTickInfos;
1969 
1970             if (rAllTickInfos.empty())
1971                 return;
1972 
1973             sal_Int32 nDepth = 0;
1974             sal_Int32 nTickmarkPropertiesCount = m_aAxisProperties.m_aTickmarkPropertiesList.size();
1975             for( auto& rTickInfos : rAllTickInfos )
1976             {
1977                 if (nDepth == nTickmarkPropertiesCount)
1978                     break;
1979 
1980                 createTickMarkLineShapes( rTickInfos, m_aAxisProperties.m_aTickmarkPropertiesList[nDepth], *pTickFactory2D, false /*bOnlyAtLabels*/ );
1981                 nDepth++;
1982             }
1983         }
1984         //create axis main lines
1985         //it serves also as the handle shape for the axis selection
1986         {
1987             drawing::PointSequenceSequence aPoints(1);
1988             apTickFactory2D->createPointSequenceForAxisMainLine( aPoints );
1989             rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
1990                     m_xGroupShape_Shapes, aPoints
1991                     , &m_aAxisProperties.m_aLineProperties );
1992             //because of this name this line will be used for marking the axis
1993             ::chart::ShapeFactory::setShapeName( xShape, u"MarkHandles"_ustr );
1994         }
1995         //create an additional line at NULL
1996         if( !AxisHelper::isAxisPositioningEnabled() )
1997         {
1998             double fExtraLineCrossesOtherAxis = getExtraLineIntersectionValue();
1999             if (!std::isnan(fExtraLineCrossesOtherAxis))
2000             {
2001                 B2DVector aStart, aEnd;
2002                 AxisLabelAlignment aLabelAlign = m_aAxisProperties.maLabelAlignment;
2003                 get2DAxisMainLine(aStart, aEnd, aLabelAlign, fExtraLineCrossesOtherAxis);
2004                 m_aAxisProperties.maLabelAlignment = aLabelAlign;
2005                 drawing::PointSequenceSequence aPoints{{
2006                         {static_cast<sal_Int32>(aStart.getX()), static_cast<sal_Int32>(aStart.getY())},
2007                         {static_cast<sal_Int32>(aEnd.getX()), static_cast<sal_Int32>(aEnd.getY())} }};
2008                 ShapeFactory::createLine2D(
2009                         m_xGroupShape_Shapes, aPoints, &m_aAxisProperties.m_aLineProperties );
2010             }
2011         }
2012     }
2013 
2014     createLabels();
2015 }
2016 
createDataTableView(std::vector<std::unique_ptr<VSeriesPlotter>> & rSeriesPlotterList,Reference<util::XNumberFormatsSupplier> const & xNumberFormatsSupplier,rtl::Reference<::chart::ChartModel> const & xChartDoc,css::uno::Reference<css::uno::XComponentContext> const & rComponentContext)2017 void VCartesianAxis::createDataTableView(std::vector<std::unique_ptr<VSeriesPlotter>>& rSeriesPlotterList,
2018                                          Reference<util::XNumberFormatsSupplier> const& xNumberFormatsSupplier,
2019                                          rtl::Reference<::chart::ChartModel> const& xChartDoc,
2020                                          css::uno::Reference<css::uno::XComponentContext> const& rComponentContext)
2021 {
2022     if (!m_aAxisProperties.m_bDisplayDataTable)
2023         return;
2024 
2025     m_pDataTableView.reset(new DataTableView(xChartDoc, m_aAxisProperties.m_xDataTableModel, rComponentContext, m_aAxisProperties.m_bDataTableAlignAxisValuesWithColumns));
2026     m_pDataTableView->initializeValues(rSeriesPlotterList);
2027     m_xNumberFormatsSupplier = xNumberFormatsSupplier;
2028 }
2029 
2030 
2031 } //namespace chart
2032 
2033 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2034