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