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