/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include "VCartesianAxis.hxx"
#include <PlottingPositionHelper.hxx>
#include <ShapeFactory.hxx>
#include <PropertyMapper.hxx>
#include <NumberFormatterWrapper.hxx>
#include <LabelPositionHelper.hxx>
#include <BaseGFXHelper.hxx>
#include <Axis.hxx>
#include <AxisHelper.hxx>
#include "Tickmarks_Equidistant.hxx"
#include <ExplicitCategoriesProvider.hxx>
#include <com/sun/star/chart2/AxisType.hpp>
#include <o3tl/safeint.hxx>
#include <rtl/math.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/color.hxx>
#include <svx/unoshape.hxx>
#include <svx/unoshtxt.hxx>
#include <VSeriesPlotter.hxx>
#include <DataTableView.hxx>
#include <ChartModel.hxx>
 
#include <comphelper/scopeguard.hxx>
 
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygonclipper.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/numeric/ftools.hxx>
 
#include <algorithm>
#include <limits>
#include <memory>
 
using namespace ::com::sun::star;
using ::com::sun::star::uno::Reference;
using ::basegfx::B2DVector;
using ::basegfx::B2DPolygon;
using ::basegfx::B2DPolyPolygon;
 
namespace chart {
 
VCartesianAxis::VCartesianAxis( const AxisProperties& rAxisProperties
            , const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier
            , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount
            , PlottingPositionHelper* pPosHelper )//takes ownership
            : VAxisBase( nDimensionIndex, nDimensionCount, rAxisProperties, xNumberFormatsSupplier )
{
    if( pPosHelper )
        m_pPosHelper = pPosHelper;
    else
        m_pPosHelper = new PlottingPositionHelper();
}
 
VCartesianAxis::~VCartesianAxis()
{
    delete m_pPosHelper;
    m_pPosHelper = nullptr;
}
 
static void lcl_ResizeTextShapeToFitAvailableSpace( SvxShapeText& rShape2DText,
                                             const AxisLabelProperties& rAxisLabelProperties,
                                             std::u16string_view rLabel,
                                             const tNameSequence& rPropNames,
                                             const tAnySequence& rPropValues,
                                             const bool bIsHorizontalAxis )
{
    bool bTextHorizontal = rAxisLabelProperties.m_fRotationAngleDegree != 0.0;
    bool bIsDirectionVertical = bIsHorizontalAxis && bTextHorizontal;
    const sal_Int32 nFullSize = bIsDirectionVertical ? rAxisLabelProperties.m_aFontReferenceSize.Height : rAxisLabelProperties.m_aFontReferenceSize.Width;
 
    if( !nFullSize || rLabel.empty() )
        return;
 
    const sal_Int32 nAvgCharWidth = rShape2DText.getSize().Width / rLabel.size();
 
    sal_Int32 nMaxLabelsSize = bIsDirectionVertical ? rAxisLabelProperties.m_aMaximumSpaceForLabels.Height : rAxisLabelProperties.m_aMaximumSpaceForLabels.Width;
 
    awt::Size aSizeAfterRotation = ShapeFactory::getSizeAfterRotation(rShape2DText, rAxisLabelProperties.m_fRotationAngleDegree);
 
    const sal_Int32 nTextSize = bIsDirectionVertical ? aSizeAfterRotation.Height : aSizeAfterRotation.Width;
 
    if( !nAvgCharWidth )
        return;
 
    static constexpr OUString sDots = u"..."_ustr;
    const sal_Int32 nCharsToRemove = ( nTextSize - nMaxLabelsSize ) / nAvgCharWidth + 1;
    sal_Int32 nNewLen = rLabel.size() - nCharsToRemove - sDots.getLength();
    // Prevent from showing only dots
    if (nNewLen < 0)
        nNewLen = ( sal_Int32(rLabel.size()) >= sDots.getLength() ) ? sDots.getLength() : rLabel.size();
 
    bool bCrop = nCharsToRemove > 0;
    if( !bCrop )
        return;
 
    OUString aNewLabel( rLabel.substr( 0, nNewLen ) );
    if( nNewLen > sDots.getLength() )
        aNewLabel += sDots;
    rShape2DText.setString( aNewLabel );
 
    PropertyMapper::setMultiProperties( rPropNames, rPropValues, rShape2DText );
}
 
static rtl::Reference<SvxShapeText> 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
          )
{
    if(rLabel.isEmpty())
        return nullptr;
 
    // #i78696# use mathematically correct rotation now
    const double fRotationAnglePi(-basegfx::deg2rad(rAxisLabelProperties.m_fRotationAngleDegree));
    uno::Any aATransformation = ShapeFactory::makeTransformation( rAnchorScreenPosition2D, fRotationAnglePi );
    OUString aLabel = ShapeFactory::getStackedString( rLabel, rAxisLabelProperties.m_bStackCharacters );
 
    rtl::Reference<SvxShapeText> xShape2DText =
                    ShapeFactory::createText( xTarget, aLabel, rPropNames, rPropValues, aATransformation );
 
    if( rAxisProperties.m_bLimitSpaceForLabels )
        lcl_ResizeTextShapeToFitAvailableSpace(*xShape2DText, rAxisLabelProperties, aLabel, rPropNames, rPropValues, bIsHorizontalAxis);
 
    LabelPositionHelper::correctPositionForRotation( xShape2DText
        , rAxisProperties.maLabelAlignment.meAlignment, rAxisLabelProperties.m_fRotationAngleDegree, rAxisProperties.m_bComplexCategories );
 
    return xShape2DText;
}
 
static bool lcl_doesShapeOverlapWithTickmark( SvxShape& rShape
                       , double fRotationAngleDegree
                       , const basegfx::B2DVector& rTickScreenPosition )
{
    ::basegfx::B2IRectangle aShapeRect = BaseGFXHelper::makeRectangle(rShape.getPosition(), ShapeFactory::getSizeAfterRotation( rShape, fRotationAngleDegree ));
 
    basegfx::B2IVector aPosition(
        static_cast<sal_Int32>( rTickScreenPosition.getX() )
        , static_cast<sal_Int32>( rTickScreenPosition.getY() ) );
    return aShapeRect.isInside(aPosition);
}
 
static void lcl_getRotatedPolygon( B2DPolygon &aPoly, const ::basegfx::B2DRectangle &aRect, const awt::Point &aPos, const double fRotationAngleDegree )
{
    aPoly = basegfx::utils::createPolygonFromRect( aRect );
 
    // For rotating the rectangle we use the opposite angle,
    // since `B2DHomMatrix` class used for
    // representing the transformation, performs rotations in the positive
    // direction (from the X axis to the Y axis). However since the coordinate
    // system used by the chart has the Y-axis pointing downward, a rotation in
    // the positive direction means a clockwise rotation. On the contrary text
    // labels are rotated counterclockwise.
    // The rotation is performed around the top-left vertex of the rectangle
    // which is then moved to its final position by using the top-left
    // vertex of the text label bounding box (aPos) as the translation vector.
    ::basegfx::B2DHomMatrix aMatrix;
    aMatrix.rotate(-basegfx::deg2rad(fRotationAngleDegree));
    aMatrix.translate( aPos.X, aPos.Y);
    aPoly.transform( aMatrix );
}
 
static bool doesOverlap( const rtl::Reference<SvxShapeText>& xShape1
                , const rtl::Reference<SvxShapeText>& xShape2
                , double fRotationAngleDegree )
{
    if( !xShape1.is() || !xShape2.is() )
        return false;
 
    ::basegfx::B2DRectangle aRect1( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape1->getSize()));
    ::basegfx::B2DRectangle aRect2( BaseGFXHelper::makeRectangle( awt::Point(0,0), xShape2->getSize()));
 
    B2DPolygon aPoly1;
    B2DPolygon aPoly2;
    lcl_getRotatedPolygon( aPoly1, aRect1, xShape1->getPosition(), fRotationAngleDegree );
    lcl_getRotatedPolygon( aPoly2, aRect2, xShape2->getPosition(), fRotationAngleDegree );
 
    B2DPolyPolygon aPolyPoly1, aPolyPoly2;
    aPolyPoly1.append( aPoly1 );
    aPolyPoly2.append( aPoly2 );
    B2DPolyPolygon overlapPoly = ::basegfx::utils::clipPolyPolygonOnPolyPolygon( aPolyPoly1, aPolyPoly2, true, false );
 
    return (overlapPoly.count() > 0);
}
 
static void removeShapesAtWrongRhythm( TickIter& rIter
                              , sal_Int32 nCorrectRhythm
                              , sal_Int32 nMaxTickToCheck
                              , const rtl::Reference< SvxShapeGroupAnyD >& xTarget )
{
    sal_Int32 nTick = 0;
    for( TickInfo* pTickInfo = rIter.firstInfo()
        ; pTickInfo && nTick <= nMaxTickToCheck
        ; pTickInfo = rIter.nextInfo(), nTick++ )
    {
        //remove labels which does not fit into the rhythm
        if( nTick%nCorrectRhythm != 0)
        {
            if(pTickInfo->xTextShape.is())
            {
                xTarget->remove(pTickInfo->xTextShape);
                pTickInfo->xTextShape = nullptr;
            }
        }
    }
}
 
namespace {
 
/**
 * If the labels are staggered and bInnerLine is true we iterate through
 * only those labels that are closer to the diagram.
 *
 * If the labels are staggered and bInnerLine is false we iterate through
 * only those that are farther from the diagram.
 *
 * If the labels are not staggered we iterate through all labels.
 */
class LabelIterator : public TickIter
{
public:
    LabelIterator( TickInfoArrayType& rTickInfoVector
            , const AxisLabelStaggering eAxisLabelStaggering
            , bool bInnerLine );
 
    virtual TickInfo*   firstInfo() override;
    virtual TickInfo*   nextInfo() override;
 
private: //member
    PureTickIter m_aPureTickIter;
    const AxisLabelStaggering   m_eAxisLabelStaggering;
    bool m_bInnerLine;
};
 
}
 
LabelIterator::LabelIterator( TickInfoArrayType& rTickInfoVector
            , const AxisLabelStaggering eAxisLabelStaggering
            , bool bInnerLine )
            : m_aPureTickIter( rTickInfoVector )
            , m_eAxisLabelStaggering(eAxisLabelStaggering)
            , m_bInnerLine(bInnerLine)
{
}
 
TickInfo* LabelIterator::firstInfo()
{
    TickInfo* pTickInfo = m_aPureTickIter.firstInfo();
    while( pTickInfo && !pTickInfo->xTextShape.is() )
        pTickInfo = m_aPureTickIter.nextInfo();
    if(!pTickInfo)
        return nullptr;
    if( (m_eAxisLabelStaggering==AxisLabelStaggering::StaggerEven && m_bInnerLine)
        ||
        (m_eAxisLabelStaggering==AxisLabelStaggering::StaggerOdd && !m_bInnerLine)
        )
    {
        //skip first label
        do
            pTickInfo = m_aPureTickIter.nextInfo();
        while( pTickInfo && !pTickInfo->xTextShape.is() );
    }
    if(!pTickInfo)
        return nullptr;
    return pTickInfo;
}
 
TickInfo* LabelIterator::nextInfo()
{
    TickInfo* pTickInfo = nullptr;
    //get next label
    do
        pTickInfo = m_aPureTickIter.nextInfo();
    while( pTickInfo && !pTickInfo->xTextShape.is() );
 
    if(  m_eAxisLabelStaggering==AxisLabelStaggering::StaggerEven
      || m_eAxisLabelStaggering==AxisLabelStaggering::StaggerOdd )
    {
        //skip one label
        do
            pTickInfo = m_aPureTickIter.nextInfo();
        while( pTickInfo && !pTickInfo->xTextShape.is() );
    }
    return pTickInfo;
}
 
static B2DVector lcl_getLabelsDistance( TickIter& rIter, const B2DVector& rDistanceTickToText, double fRotationAngleDegree )
{
    //calculates the height or width of a line of labels
    //thus a following line of labels can be shifted for that distance
 
    B2DVector aRet(0,0);
 
    sal_Int32 nDistanceTickToText = static_cast<sal_Int32>( rDistanceTickToText.getLength() );
    if( nDistanceTickToText==0.0)
        return aRet;
 
    B2DVector aStaggerDirection(rDistanceTickToText);
    aStaggerDirection.normalize();
 
    sal_Int32 nDistance=0;
    rtl::Reference< SvxShapeText >  xShape2DText;
    for( TickInfo* pTickInfo = rIter.firstInfo()
        ; pTickInfo
        ; pTickInfo = rIter.nextInfo() )
    {
        xShape2DText = pTickInfo->xTextShape;
        if( xShape2DText.is() )
        {
            awt::Size aSize = ShapeFactory::getSizeAfterRotation( *xShape2DText, fRotationAngleDegree );
            if(fabs(aStaggerDirection.getX())>fabs(aStaggerDirection.getY()))
                nDistance = std::max(nDistance,aSize.Width);
            else
                nDistance = std::max(nDistance,aSize.Height);
        }
    }
 
    aRet = aStaggerDirection*nDistance;
 
    //add extra distance for vertical distance
    if(fabs(aStaggerDirection.getX())>fabs(aStaggerDirection.getY()))
        aRet += rDistanceTickToText;
 
    return aRet;
}
 
static void lcl_shiftLabels( TickIter& rIter, const B2DVector& rStaggerDistance )
{
    if(rStaggerDistance.getLength()==0.0)
        return;
    for( TickInfo* pTickInfo = rIter.firstInfo()
        ; pTickInfo
        ; pTickInfo = rIter.nextInfo() )
    {
        const rtl::Reference<SvxShapeText>& xShape2DText = pTickInfo->xTextShape;
        if( xShape2DText.is() )
        {
            awt::Point aPos  = xShape2DText->getPosition();
            aPos.X += static_cast<sal_Int32>(rStaggerDistance.getX());
            aPos.Y += static_cast<sal_Int32>(rStaggerDistance.getY());
            xShape2DText->setPosition( aPos );
        }
    }
}
 
static bool lcl_hasWordBreak( const rtl::Reference<SvxShapeText>& xShape )
{
    if (!xShape.is())
        return false;
 
    SvxTextEditSource* pTextEditSource = dynamic_cast<SvxTextEditSource*>(xShape->GetEditSource());
    if (!pTextEditSource)
        return false;
 
    pTextEditSource->UpdateOutliner();
    SvxTextForwarder* pTextForwarder = pTextEditSource->GetTextForwarder();
    if (!pTextForwarder)
        return false;
 
    sal_Int32 nParaCount = pTextForwarder->GetParagraphCount();
    for ( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
    {
        sal_Int32 nLineCount = pTextForwarder->GetLineCount( nPara );
        for ( sal_Int32 nLine = 0; nLine < nLineCount; ++nLine )
        {
            sal_Int32 nLineStart = 0;
            sal_Int32 nLineEnd = 0;
            pTextForwarder->GetLineBoundaries( nLineStart, nLineEnd, nPara, nLine );
            assert(nLineStart >= 0);
            sal_Int32 nWordStart = 0;
            sal_Int32 nWordEnd = 0;
            if ( pTextForwarder->GetWordIndices( nPara, nLineStart, nWordStart, nWordEnd ) &&
                 ( nWordStart != nLineStart ) )
            {
                return true;
            }
        }
    }
 
    return false;
}
 
static OUString getTextLabelString(
    const FixedNumberFormatter& rFixedNumberFormatter, const uno::Sequence<OUString>* pCategories,
    const TickInfo* pTickInfo, bool bComplexCat, Color& rExtraColor, bool& rHasExtraColor )
{
    if (pCategories)
    {
        // This is a normal category axis.  Get the label string from the
        // label string array.
        sal_Int32 nIndex = static_cast<sal_Int32>(pTickInfo->getUnscaledTickValue()) - 1; //first category (index 0) matches with real number 1.0
        if( nIndex>=0 && nIndex<pCategories->getLength() )
            return (*pCategories)[nIndex];
 
        return OUString();
    }
    else if (bComplexCat)
    {
        // This is a complex category axis.  The label is stored in the tick.
        return pTickInfo->aText;
    }
 
    // This is a numeric axis.  Format the original tick value per number format.
    return rFixedNumberFormatter.getFormattedString(pTickInfo->getUnscaledTickValue(), rExtraColor, rHasExtraColor);
}
 
static void getAxisLabelProperties(
    tNameSequence& rPropNames, tAnySequence& rPropValues, const AxisProperties& rAxisProp,
    const AxisLabelProperties& rAxisLabelProp,
    sal_Int32 nLimitedSpaceForText, bool bLimitedHeight )
{
    Reference<beans::XPropertySet> xProps(rAxisProp.m_xAxisModel);
 
    PropertyMapper::getTextLabelMultiPropertyLists(
        xProps, rPropNames, rPropValues, false, nLimitedSpaceForText, bLimitedHeight, false);
 
    LabelPositionHelper::doDynamicFontResize(
        rPropValues, rPropNames, xProps, rAxisLabelProp.m_aFontReferenceSize);
 
    LabelPositionHelper::changeTextAdjustment(
        rPropValues, rPropNames, rAxisProp.maLabelAlignment.meAlignment);
}
 
namespace {
 
/**
 * Iterate through only 3 ticks including the one that has the longest text
 * length.  When the first tick has the longest text, it iterates through
 * the first 3 ticks.  Otherwise it iterates through 3 ticks such that the
 * 2nd tick is the one with the longest text.
 */
class MaxLabelTickIter : public TickIter
{
public:
    MaxLabelTickIter( TickInfoArrayType& rTickInfoVector, size_t nLongestLabelIndex );
 
    virtual TickInfo* firstInfo() override;
    virtual TickInfo* nextInfo() override;
 
private:
    TickInfoArrayType& m_rTickInfoVector;
    std::vector<size_t> m_aValidIndices;
    size_t m_nCurrentIndex;
};
 
}
 
MaxLabelTickIter::MaxLabelTickIter(
    TickInfoArrayType& rTickInfoVector, size_t nLongestLabelIndex ) :
    m_rTickInfoVector(rTickInfoVector), m_nCurrentIndex(0)
{
    assert(!rTickInfoVector.empty()); // should be checked by the caller.
    assert(nLongestLabelIndex < rTickInfoVector.size());
 
    size_t nMaxIndex = m_rTickInfoVector.size()-1;
    if (nLongestLabelIndex >= nMaxIndex-1)
        nLongestLabelIndex = 0;
 
    if (nLongestLabelIndex > 0)
        m_aValidIndices.push_back(nLongestLabelIndex-1);
 
    m_aValidIndices.push_back(nLongestLabelIndex);
 
    while (m_aValidIndices.size() < 3)
    {
        ++nLongestLabelIndex;
        if (nLongestLabelIndex > nMaxIndex)
            break;
 
        m_aValidIndices.push_back(nLongestLabelIndex);
    }
}
 
TickInfo* MaxLabelTickIter::firstInfo()
{
    m_nCurrentIndex = 0;
    if (m_nCurrentIndex < m_aValidIndices.size())
        return &m_rTickInfoVector[m_aValidIndices[m_nCurrentIndex]];
    return nullptr;
}
 
TickInfo* MaxLabelTickIter::nextInfo()
{
    m_nCurrentIndex++;
    if (m_nCurrentIndex < m_aValidIndices.size())
        return &m_rTickInfoVector[m_aValidIndices[m_nCurrentIndex]];
    return nullptr;
}
 
bool VCartesianAxis::isBreakOfLabelsAllowed(
    const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis) const
{
    if( m_aTextLabels.getLength() > 100 )
        return false;
    if( !rAxisLabelProperties.m_bLineBreakAllowed )
        return false;
    if( rAxisLabelProperties.m_bStackCharacters )
        return false;
    //no break for value axis
    if( !m_bUseTextLabels )
        return false;
    if( !( rAxisLabelProperties.m_fRotationAngleDegree == 0.0 ||
           rAxisLabelProperties.m_fRotationAngleDegree == 90.0 ||
           rAxisLabelProperties.m_fRotationAngleDegree == 270.0 ) )
        return false;
    //no break for complex vertical category axis
    if( !m_aAxisProperties.m_bSwapXAndY )
        return bIsHorizontalAxis;
    else if( m_aAxisProperties.m_bSwapXAndY && !m_aAxisProperties.m_bComplexCategories )
        return bIsVerticalAxis;
    else
        return false;
}
namespace{
 
bool canAutoAdjustLabelPlacement(
    const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis)
{
    // joined prerequisite checks for auto rotate and auto stagger
    if( rAxisLabelProperties.m_bOverlapAllowed )
        return false;
    if( rAxisLabelProperties.m_bLineBreakAllowed ) // auto line break may conflict with...
        return false;
    if( rAxisLabelProperties.m_fRotationAngleDegree != 0.0 )
        return false;
    // automatic adjusting labels only works for
    // horizontal axis with horizontal text
    // or vertical axis with vertical text
    if( bIsHorizontalAxis )
        return !rAxisLabelProperties.m_bStackCharacters;
    if( bIsVerticalAxis )
        return rAxisLabelProperties.m_bStackCharacters;
    return false;
}
 
bool isAutoStaggeringOfLabelsAllowed(
    const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis )
{
    if( rAxisLabelProperties.m_eStaggering != AxisLabelStaggering::StaggerAuto )
        return false;
    return canAutoAdjustLabelPlacement(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis);
}
 
// make clear that we check for auto rotation prerequisites
const auto& isAutoRotatingOfLabelsAllowed = canAutoAdjustLabelPlacement;
 
} // namespace
void VCartesianAxis::createAllTickInfosFromComplexCategories( TickInfoArraysType& rAllTickInfos, bool bShiftedPosition )
{
    //no minor tickmarks will be generated!
    //order is: inner labels first , outer labels last (that is different to all other TickIter cases)
    if(!bShiftedPosition)
    {
        rAllTickInfos.clear();
        sal_Int32 nLevel=0;
        sal_Int32 nLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount();
        for( ; nLevel<nLevelCount; nLevel++ )
        {
            TickInfoArrayType aTickInfoVector;
            const std::vector<ComplexCategory>* pComplexCategories =
                m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoriesByLevel(nLevel);
 
            if (!pComplexCategories)
                continue;
 
            sal_Int32 nCatIndex = 0;
 
            for (auto const& complexCategory : *pComplexCategories)
            {
                TickInfo aTickInfo(nullptr);
                sal_Int32 nCount = complexCategory.Count;
                if( nCatIndex + 1.0 + nCount >= m_aScale.Maximum )
                {
                    nCount = static_cast<sal_Int32>(m_aScale.Maximum - 1.0 - nCatIndex);
                    if( nCount <= 0 )
                        nCount = 1;
                }
                aTickInfo.fScaledTickValue = nCatIndex + 1.0 + nCount/2.0;
                aTickInfo.nFactorForLimitedTextWidth = nCount;
                aTickInfo.aText = complexCategory.Text;
                aTickInfoVector.push_back(aTickInfo);
                nCatIndex += nCount;
                if( nCatIndex + 1.0 >= m_aScale.Maximum )
                    break;
            }
            rAllTickInfos.push_back(aTickInfoVector);
        }
    }
    else //bShiftedPosition==false
    {
        rAllTickInfos.clear();
        sal_Int32 nLevel=0;
        sal_Int32 nLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount();
        for( ; nLevel<nLevelCount; nLevel++ )
        {
            TickInfoArrayType aTickInfoVector;
            const std::vector<ComplexCategory>* pComplexCategories =
                m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoriesByLevel(nLevel);
            sal_Int32 nCatIndex = 0;
            if (pComplexCategories)
            {
                for (auto const& complexCategory : *pComplexCategories)
                {
                    TickInfo aTickInfo(nullptr);
                    aTickInfo.fScaledTickValue = nCatIndex + 1.0;
                    aTickInfoVector.push_back(aTickInfo);
                    nCatIndex += complexCategory.Count;
                    if( nCatIndex + 1.0 > m_aScale.Maximum )
                        break;
                }
            }
 
            //fill up with single ticks until maximum scale
            while( nCatIndex + 1.0 < m_aScale.Maximum )
            {
                TickInfo aTickInfo(nullptr);
                aTickInfo.fScaledTickValue = nCatIndex + 1.0;
                aTickInfoVector.push_back(aTickInfo);
                nCatIndex ++;
                if( nLevel>0 )
                    break;
            }
            //add an additional tick at the end
            {
                TickInfo aTickInfo(nullptr);
                aTickInfo.fScaledTickValue = m_aScale.Maximum;
                aTickInfoVector.push_back(aTickInfo);
            }
            rAllTickInfos.push_back(aTickInfoVector);
        }
    }
}
 
void VCartesianAxis::createAllTickInfos( TickInfoArraysType& rAllTickInfos )
{
    if( isComplexCategoryAxis() )
        createAllTickInfosFromComplexCategories( rAllTickInfos, false );
    else
        VAxisBase::createAllTickInfos(rAllTickInfos);
}
 
TickIter* VCartesianAxis::createLabelTickIterator( sal_Int32 nTextLevel )
{
    if( nTextLevel>=0 && o3tl::make_unsigned(nTextLevel) < m_aAllTickInfos.size() )
        return new PureTickIter( m_aAllTickInfos[nTextLevel] );
    return nullptr;
}
 
TickIter* VCartesianAxis::createMaximumLabelTickIterator( sal_Int32 nTextLevel )
{
    if( isComplexCategoryAxis() || isDateAxis() )
    {
        return createLabelTickIterator( nTextLevel ); //mmmm maybe todo: create less than all texts here
    }
    else
    {
        if(nTextLevel==0)
        {
            if( !m_aAllTickInfos.empty() )
            {
                size_t nLongestLabelIndex = m_bUseTextLabels ? getIndexOfLongestLabel(m_aTextLabels) : 0;
                if (nLongestLabelIndex >= m_aAllTickInfos[0].size())
                    return nullptr;
 
                return new MaxLabelTickIter( m_aAllTickInfos[0], nLongestLabelIndex );
            }
        }
    }
    return nullptr;
}
 
sal_Int32 VCartesianAxis::getTextLevelCount() const
{
    sal_Int32 nTextLevelCount = 1;
    if( isComplexCategoryAxis() )
        nTextLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount();
    return nTextLevelCount;
}
 
bool VCartesianAxis::createTextShapes(
    const rtl::Reference< SvxShapeGroupAnyD >& xTarget, TickIter& rTickIter,
    AxisLabelProperties& rAxisLabelProperties, TickFactory2D const * pTickFactory,
    sal_Int32 nScreenDistanceBetweenTicks )
{
    const bool bIsHorizontalAxis = pTickFactory->isHorizontalAxis();
    const bool bIsVerticalAxis = pTickFactory->isVerticalAxis();
 
    if( m_bUseTextLabels && (m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_NEAR_AXIS ||
        m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START))
    {
        if (bIsHorizontalAxis)
        {
            rAxisLabelProperties.m_aMaximumSpaceForLabels.Y = pTickFactory->getXaxisStartPos().getY();
            rAxisLabelProperties.m_aMaximumSpaceForLabels.Height = rAxisLabelProperties.m_aFontReferenceSize.Height - rAxisLabelProperties.m_aMaximumSpaceForLabels.Y;
        }
        else if (bIsVerticalAxis)
        {
            rAxisLabelProperties.m_aMaximumSpaceForLabels.X = 0;
            rAxisLabelProperties.m_aMaximumSpaceForLabels.Width = pTickFactory->getXaxisStartPos().getX();
        }
    }
 
    bool bIsBreakOfLabelsAllowed = isBreakOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis );
    if (!bIsBreakOfLabelsAllowed &&
        !isAutoStaggeringOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) &&
        !rAxisLabelProperties.isStaggered())
    {
        return createTextShapesSimple(xTarget, rTickIter, rAxisLabelProperties, pTickFactory);
    }
 
    FixedNumberFormatter aFixedNumberFormatter(
                m_xNumberFormatsSupplier, rAxisLabelProperties.m_nNumberFormatKey );
 
    bool bIsStaggered = rAxisLabelProperties.isStaggered();
    B2DVector aTextToTickDistance = pTickFactory->getDistanceAxisTickToText(m_aAxisProperties, true);
    sal_Int32 nLimitedSpaceForText = -1;
 
    if (bIsBreakOfLabelsAllowed)
    {
        if (!m_aAxisProperties.m_bLimitSpaceForLabels)
        {
            basegfx::B2DVector nDeltaVector = pTickFactory->getXaxisEndPos() - pTickFactory->getXaxisStartPos();
            nLimitedSpaceForText = nDeltaVector.getX();
        }
        if (nScreenDistanceBetweenTicks > 0)
            nLimitedSpaceForText = nScreenDistanceBetweenTicks;
 
        if( bIsStaggered )
            nLimitedSpaceForText *= 2;
 
        if( nLimitedSpaceForText > 0 )
        { //reduce space for a small amount to have a visible distance between the labels:
            sal_Int32 nReduce = (nLimitedSpaceForText*5)/100;
            if(!nReduce)
                nReduce = 1;
            nLimitedSpaceForText -= nReduce;
        }
 
        // recalculate the nLimitedSpaceForText in case of 90 and 270 degree if the text break is true
        if ( rAxisLabelProperties.m_fRotationAngleDegree == 90.0 || rAxisLabelProperties.m_fRotationAngleDegree == 270.0 )
        {
            nLimitedSpaceForText = rAxisLabelProperties.m_aMaximumSpaceForLabels.Height;
            m_aAxisProperties.m_bLimitSpaceForLabels = false;
        }
 
        // recalculate the nLimitedSpaceForText in case of vertical category axis if the text break is true
        if ( m_aAxisProperties.m_bSwapXAndY && bIsVerticalAxis && rAxisLabelProperties.m_fRotationAngleDegree == 0.0 )
        {
            nLimitedSpaceForText = pTickFactory->getXaxisStartPos().getX();
            m_aAxisProperties.m_bLimitSpaceForLabels = false;
        }
    }
 
    // Stores an array of text label strings in case of a normal
    // (non-complex) category axis.
    const uno::Sequence<OUString>* pCategories = nullptr;
    if( m_bUseTextLabels && !m_aAxisProperties.m_bComplexCategories )
        pCategories = &m_aTextLabels;
 
    bool bLimitedHeight;
    if( !m_aAxisProperties.m_bSwapXAndY )
        bLimitedHeight = fabs(aTextToTickDistance.getX()) > fabs(aTextToTickDistance.getY());
    else
        bLimitedHeight = fabs(aTextToTickDistance.getX()) < fabs(aTextToTickDistance.getY());
    //prepare properties for multipropertyset-interface of shape
    tNameSequence aPropNames;
    tAnySequence aPropValues;
    getAxisLabelProperties(aPropNames, aPropValues, m_aAxisProperties, rAxisLabelProperties, nLimitedSpaceForText, bLimitedHeight);
 
    uno::Any* pColorAny = PropertyMapper::getValuePointer(aPropValues,aPropNames,u"CharColor");
    Color nColor = COL_AUTO;
    if(pColorAny)
        *pColorAny >>= nColor;
 
    uno::Any* pLimitedSpaceAny = PropertyMapper::getValuePointerForLimitedSpace(aPropValues,aPropNames,bLimitedHeight);
 
    const TickInfo* pPreviousVisibleTickInfo = nullptr;
    const TickInfo* pPREPreviousVisibleTickInfo = nullptr;
    sal_Int32 nTick = 0;
    for( TickInfo* pTickInfo = rTickIter.firstInfo()
        ; pTickInfo
        ; pTickInfo = rTickIter.nextInfo(), nTick++ )
    {
        const TickInfo* pLastVisibleNeighbourTickInfo = bIsStaggered ?
                    pPREPreviousVisibleTickInfo : pPreviousVisibleTickInfo;
 
        //don't create labels which does not fit into the rhythm
        if( nTick%rAxisLabelProperties.m_nRhythm != 0 )
            continue;
 
        //don't create labels for invisible ticks
        if( !pTickInfo->bPaintIt )
            continue;
 
        if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
        {
            // Overlapping is not allowed.  If the label overlaps with its
            // neighboring label, try increasing the tick interval (or rhythm
            // as it's called) and start over.
 
            if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
                       , rAxisLabelProperties.m_fRotationAngleDegree
                       , pTickInfo->aTickScreenPosition ) )
            {
                // This tick overlaps with its neighbor.  Try to stagger (if
                // auto staggering is allowed) to avoid overlapping.
 
                bool bOverlapsAfterAutoStagger = true;
                if( !bIsStaggered && isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis ) )
                {
                    bIsStaggered = true;
                    rAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
                    pLastVisibleNeighbourTickInfo = pPREPreviousVisibleTickInfo;
                    if( !pLastVisibleNeighbourTickInfo ||
                        !lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
                                , rAxisLabelProperties.m_fRotationAngleDegree
                                , pTickInfo->aTickScreenPosition ) )
                        bOverlapsAfterAutoStagger = false;
                }
 
                if (bOverlapsAfterAutoStagger)
                {
                    // Still overlaps with its neighbor even after staggering.
                    // Increment the visible tick intervals (if that's
                    // allowed) and start over.
 
                    rAxisLabelProperties.m_nRhythm++;
                    removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
                    return false;
                }
            }
        }
 
        bool bHasExtraColor=false;
        Color nExtraColor;
 
        OUString aLabel = getTextLabelString(
            aFixedNumberFormatter, pCategories, pTickInfo, isComplexCategoryAxis(),
            nExtraColor, bHasExtraColor);
 
        if(pColorAny)
            *pColorAny <<= bHasExtraColor?nExtraColor:nColor;
        if(pLimitedSpaceAny)
            *pLimitedSpaceAny <<= sal_Int32(nLimitedSpaceForText*pTickInfo->nFactorForLimitedTextWidth);
 
        B2DVector aTickScreenPos2D = pTickInfo->aTickScreenPosition;
        aTickScreenPos2D += aTextToTickDistance;
        awt::Point aAnchorScreenPosition2D(
            static_cast<sal_Int32>(aTickScreenPos2D.getX())
            ,static_cast<sal_Int32>(aTickScreenPos2D.getY()));
 
        //create single label
        if(!pTickInfo->xTextShape.is())
        {
            pTickInfo->xTextShape = createSingleLabel( xTarget
                                    , aAnchorScreenPosition2D, aLabel
                                    , rAxisLabelProperties, m_aAxisProperties
                                    , aPropNames, aPropValues, bIsHorizontalAxis );
        }
        if(!pTickInfo->xTextShape.is())
            continue;
 
        recordMaximumTextSize( *pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree );
 
        // Label has multiple lines and the words are broken
        if (nLimitedSpaceForText > 0
                && !rAxisLabelProperties.m_bOverlapAllowed
                && rAxisLabelProperties.m_fRotationAngleDegree == 0.0
                && nTick > 0
                && lcl_hasWordBreak(pTickInfo->xTextShape))
        {
            // Label has multiple lines and belongs to a complex category
            // axis. Rotate 90 degrees to try to avoid overlaps.
            if ( m_aAxisProperties.m_bComplexCategories )
            {
                rAxisLabelProperties.m_fRotationAngleDegree = 90;
            }
            rAxisLabelProperties.m_bLineBreakAllowed = false;
            m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree;
            removeTextShapesFromTicks();
            return false;
        }
 
        //if NO OVERLAP -> remove overlapping shapes
        if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
        {
            // Check if the label still overlaps with its neighbor.
            if( doesOverlap( pLastVisibleNeighbourTickInfo->xTextShape, pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree ) )
            {
                // It overlaps.  Check if staggering helps.
                bool bOverlapsAfterAutoStagger = true;
                if( !bIsStaggered && isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis ) )
                {
                    // Compatibility option: starting from LibreOffice 5.1 the rotated
                    // layout is preferred to staggering for axis labels.
                    if( !isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis)
                        || m_aAxisProperties.m_bTryStaggeringFirst )
                    {
                        bIsStaggered = true;
                        rAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
                        pLastVisibleNeighbourTickInfo = pPREPreviousVisibleTickInfo;
                        if( !pLastVisibleNeighbourTickInfo ||
                            !lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
                                , rAxisLabelProperties.m_fRotationAngleDegree
                                , pTickInfo->aTickScreenPosition ) )
                            bOverlapsAfterAutoStagger = false;
                    }
                }
 
                if (bOverlapsAfterAutoStagger)
                {
                    // Staggering didn't solve the overlap.
                    if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) )
                    {
                        // Try auto-rotating the labels at 45 degrees and
                        // start over.  This rotation angle will be stored for
                        // all future text shape creation runs.
                        // The nRhythm parameter is reset to 1 since the layout
                        // used for text labels is changed.
                        rAxisLabelProperties.autoRotate45();
                        m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree; // Store it for future runs.
                        removeTextShapesFromTicks();
                        rAxisLabelProperties.m_nRhythm = 1;
                        return false;
                    }
 
                    // Try incrementing the tick interval and start over.
                    rAxisLabelProperties.m_nRhythm++;
                    removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
                    return false;
                }
            }
        }
 
        pPREPreviousVisibleTickInfo = pPreviousVisibleTickInfo;
        pPreviousVisibleTickInfo = pTickInfo;
    }
    return true;
}
 
bool VCartesianAxis::createTextShapesSimple(
    const rtl::Reference< SvxShapeGroupAnyD >& xTarget, TickIter& rTickIter,
    AxisLabelProperties& rAxisLabelProperties, TickFactory2D const * pTickFactory )
{
    FixedNumberFormatter aFixedNumberFormatter(
                m_xNumberFormatsSupplier, rAxisLabelProperties.m_nNumberFormatKey );
 
    const bool bIsHorizontalAxis = pTickFactory->isHorizontalAxis();
    const bool bIsVerticalAxis = pTickFactory->isVerticalAxis();
    B2DVector aTextToTickDistance = pTickFactory->getDistanceAxisTickToText(m_aAxisProperties, true);
 
     // Stores an array of text label strings in case of a normal
     // (non-complex) category axis.
    const uno::Sequence<OUString>* pCategories = nullptr;
    if( m_bUseTextLabels && !m_aAxisProperties.m_bComplexCategories )
        pCategories = &m_aTextLabels;
 
    bool bLimitedHeight = fabs(aTextToTickDistance.getX()) > fabs(aTextToTickDistance.getY());
 
    //prepare properties for multipropertyset-interface of shape
    tNameSequence aPropNames;
    tAnySequence aPropValues;
    getAxisLabelProperties(aPropNames, aPropValues, m_aAxisProperties, rAxisLabelProperties, -1, bLimitedHeight);
 
    uno::Any* pColorAny = PropertyMapper::getValuePointer(aPropValues,aPropNames,u"CharColor");
    Color nColor = COL_AUTO;
    if(pColorAny)
        *pColorAny >>= nColor;
 
    uno::Any* pLimitedSpaceAny = PropertyMapper::getValuePointerForLimitedSpace(aPropValues,aPropNames,bLimitedHeight);
 
    const TickInfo* pPreviousVisibleTickInfo = nullptr;
    sal_Int32 nTick = 0;
    for( TickInfo* pTickInfo = rTickIter.firstInfo()
        ; pTickInfo
        ; pTickInfo = rTickIter.nextInfo(), nTick++ )
    {
        const TickInfo* pLastVisibleNeighbourTickInfo = pPreviousVisibleTickInfo;
 
        //don't create labels which does not fit into the rhythm
        if( nTick%rAxisLabelProperties.m_nRhythm != 0 )
            continue;
 
        //don't create labels for invisible ticks
        if( !pTickInfo->bPaintIt )
            continue;
 
        if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
        {
            // Overlapping is not allowed.  If the label overlaps with its
            // neighboring label, try increasing the tick interval (or rhythm
            // as it's called) and start over.
 
            if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
                       , rAxisLabelProperties.m_fRotationAngleDegree
                       , pTickInfo->aTickScreenPosition ) )
            {
                // This tick overlaps with its neighbor. Increment the visible
                // tick intervals (if that's allowed) and start over.
 
                rAxisLabelProperties.m_nRhythm++;
                removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
                return false;
            }
        }
 
        bool bHasExtraColor=false;
        Color nExtraColor;
 
        OUString aLabel = getTextLabelString(
            aFixedNumberFormatter, pCategories, pTickInfo, isComplexCategoryAxis(),
            nExtraColor, bHasExtraColor);
 
        if(pColorAny)
            *pColorAny <<= bHasExtraColor?nExtraColor:nColor;
        if(pLimitedSpaceAny)
            *pLimitedSpaceAny <<= sal_Int32(-1*pTickInfo->nFactorForLimitedTextWidth);
 
        B2DVector aTickScreenPos2D = pTickInfo->aTickScreenPosition;
        aTickScreenPos2D += aTextToTickDistance;
        awt::Point aAnchorScreenPosition2D(
            static_cast<sal_Int32>(aTickScreenPos2D.getX())
            ,static_cast<sal_Int32>(aTickScreenPos2D.getY()));
 
        //create single label
        if(!pTickInfo->xTextShape.is())
            pTickInfo->xTextShape = createSingleLabel( xTarget
                                    , aAnchorScreenPosition2D, aLabel
                                    , rAxisLabelProperties, m_aAxisProperties
                                    , aPropNames, aPropValues, bIsHorizontalAxis );
        if(!pTickInfo->xTextShape.is())
            continue;
 
        recordMaximumTextSize( *pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree );
 
        //if NO OVERLAP -> remove overlapping shapes
        if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
        {
            // Check if the label still overlaps with its neighbor.
            if( doesOverlap( pLastVisibleNeighbourTickInfo->xTextShape, pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree ) )
            {
                // It overlaps.
                if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) )
                {
                    // Try auto-rotating the labels at 45 degrees and
                    // start over.  This rotation angle will be stored for
                    // all future text shape creation runs.
                    // The nRhythm parameter is reset to 1 since the layout
                    // used for text labels is changed.
                    rAxisLabelProperties.autoRotate45();
                    m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree; // Store it for future runs.
                    removeTextShapesFromTicks();
                    rAxisLabelProperties.m_nRhythm = 1;
                    return false;
                }
 
                // Try incrementing the tick interval and start over.
                rAxisLabelProperties.m_nRhythm++;
                removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget );
                return false;
            }
        }
 
        pPreviousVisibleTickInfo = pTickInfo;
    }
    return true;
}
 
double VCartesianAxis::getAxisIntersectionValue() const
{
    if (m_aAxisProperties.m_pfMainLinePositionAtOtherAxis)
        return *m_aAxisProperties.m_pfMainLinePositionAtOtherAxis;
 
    double fMin = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMinY();
    double fMax = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMaxY();
 
    return (m_aAxisProperties.m_eCrossoverType == css::chart::ChartAxisPosition_END) ? fMax : fMin;
}
 
double VCartesianAxis::getLabelLineIntersectionValue() const
{
    if (m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START)
        return (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMinY();
 
    if (m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END)
        return (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMaxY();
 
    return getAxisIntersectionValue();
}
 
double VCartesianAxis::getExtraLineIntersectionValue() const
{
    if( !m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis )
        return std::numeric_limits<double>::quiet_NaN();
 
    double fMin = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMinY();
    double fMax = (m_nDimensionIndex==1) ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMaxY();
 
    if( *m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis <= fMin
        || *m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis >= fMax )
        return std::numeric_limits<double>::quiet_NaN();
 
    return *m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis;
}
 
B2DVector VCartesianAxis::getScreenPosition( double fLogicX, double fLogicY, double fLogicZ ) const
{
    B2DVector aRet(0,0);
 
    if( m_pPosHelper )
    {
        drawing::Position3D aScenePos = m_pPosHelper->transformLogicToScene( fLogicX, fLogicY, fLogicZ, true );
        if(m_nDimension==3)
        {
            if (m_xLogicTarget.is())
            {
                tPropertyNameMap aDummyPropertyNameMap;
                rtl::Reference<Svx3DExtrudeObject> xShape3DAnchor = ShapeFactory::createCube( m_xLogicTarget
                        , aScenePos,drawing::Direction3D(1,1,1), 0, nullptr, aDummyPropertyNameMap);
                awt::Point a2DPos = xShape3DAnchor->getPosition(); //get 2D position from xShape3DAnchor
                m_xLogicTarget->remove(xShape3DAnchor);
                aRet.setX( a2DPos.X );
                aRet.setY( a2DPos.Y );
            }
            else
            {
                OSL_FAIL("cannot calculate screen position in VCartesianAxis::getScreenPosition");
            }
        }
        else
        {
            aRet.setX( aScenePos.PositionX );
            aRet.setY( aScenePos.PositionY );
        }
    }
 
    return aRet;
}
 
VCartesianAxis::ScreenPosAndLogicPos VCartesianAxis::getScreenPosAndLogicPos( double fLogicX_, double fLogicY_, double fLogicZ_ ) const
{
    ScreenPosAndLogicPos aRet;
    aRet.fLogicX = fLogicX_;
    aRet.fLogicY = fLogicY_;
    aRet.fLogicZ = fLogicZ_;
    aRet.aScreenPos = getScreenPosition( fLogicX_, fLogicY_, fLogicZ_ );
    return aRet;
}
 
typedef std::vector< VCartesianAxis::ScreenPosAndLogicPos > tScreenPosAndLogicPosList;
 
namespace {
 
struct lcl_LessXPos
{
    bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos& rPos1, const VCartesianAxis::ScreenPosAndLogicPos& rPos2 )
    {
        return ( rPos1.aScreenPos.getX() < rPos2.aScreenPos.getX() );
    }
};
 
struct lcl_GreaterYPos
{
    bool operator() ( const VCartesianAxis::ScreenPosAndLogicPos& rPos1, const VCartesianAxis::ScreenPosAndLogicPos& rPos2 )
    {
        return ( rPos1.aScreenPos.getY() > rPos2.aScreenPos.getY() );
    }
};
 
}
 
void VCartesianAxis::get2DAxisMainLine(
    B2DVector& rStart, B2DVector& rEnd, AxisLabelAlignment& rAlignment, double fCrossesOtherAxis ) const
{
    //m_aAxisProperties might get updated and changed here because
    //    the label alignment and inner direction sign depends exactly of the choice of the axis line position which is made here in this method
 
    double const fMinX = m_pPosHelper->getLogicMinX();
    double const fMinY = m_pPosHelper->getLogicMinY();
    double const fMinZ = m_pPosHelper->getLogicMinZ();
    double const fMaxX = m_pPosHelper->getLogicMaxX();
    double const fMaxY = m_pPosHelper->getLogicMaxY();
    double const fMaxZ = m_pPosHelper->getLogicMaxZ();
 
    double fXOnXPlane = fMinX;
    double fXOther = fMaxX;
    int nDifferentValue = !m_pPosHelper->isMathematicalOrientationX() ? -1 : 1;
    if( !m_pPosHelper->isSwapXAndY() )
        nDifferentValue *= (m_eLeftWallPos != CuboidPlanePosition_Left) ? -1 : 1;
    else
        nDifferentValue *= (m_eBottomPos != CuboidPlanePosition_Bottom) ? -1 : 1;
    if( nDifferentValue<0 )
    {
        fXOnXPlane = fMaxX;
        fXOther = fMinX;
    }
 
    double fYOnYPlane = fMinY;
    double fYOther = fMaxY;
    nDifferentValue = !m_pPosHelper->isMathematicalOrientationY() ? -1 : 1;
    if( !m_pPosHelper->isSwapXAndY() )
        nDifferentValue *= (m_eBottomPos != CuboidPlanePosition_Bottom) ? -1 : 1;
    else
        nDifferentValue *= (m_eLeftWallPos != CuboidPlanePosition_Left) ? -1 : 1;
    if( nDifferentValue<0 )
    {
        fYOnYPlane = fMaxY;
        fYOther = fMinY;
    }
 
    double fZOnZPlane = fMaxZ;
    double fZOther = fMinZ;
    nDifferentValue = !m_pPosHelper->isMathematicalOrientationZ() ? -1 : 1;
    nDifferentValue *= (m_eBackWallPos != CuboidPlanePosition_Back) ? -1 : 1;
    if( nDifferentValue<0 )
    {
        fZOnZPlane = fMinZ;
        fZOther = fMaxZ;
    }
 
    double fXStart = fMinX;
    double fYStart = fMinY;
    double fZStart = fMinZ;
    double fXEnd;
    double fYEnd;
    double fZEnd = fZStart;
 
    if( m_nDimensionIndex==0 ) //x-axis
    {
        if( fCrossesOtherAxis < fMinY )
            fCrossesOtherAxis = fMinY;
        else if( fCrossesOtherAxis > fMaxY )
            fCrossesOtherAxis = fMaxY;
 
        fYStart = fYEnd = fCrossesOtherAxis;
        fXEnd=m_pPosHelper->getLogicMaxX();
 
        if(m_nDimension==3)
        {
            if( AxisHelper::isAxisPositioningEnabled() )
            {
                if( ::rtl::math::approxEqual( fYOther, fYStart) )
                    fZStart = fZEnd = fZOnZPlane;
                else
                    fZStart = fZEnd = fZOther;
            }
            else
            {
                rStart = getScreenPosition( fXStart, fYStart, fZStart );
                rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
 
                double fDeltaX = rEnd.getX() - rStart.getX();
                double fDeltaY = rEnd.getY() - rStart.getY();
 
                //only those points are candidates which are lying on exactly one wall as these are outer edges
                tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fMinX, fYOnYPlane, fZOther ), getScreenPosAndLogicPos( fMinX, fYOther, fZOnZPlane ) };
 
                if( fabs(fDeltaY) > fabs(fDeltaX)  )
                {
                    rAlignment.meAlignment = LABEL_ALIGN_LEFT;
                    //choose most left positions
                    std::sort( aPosList.begin(), aPosList.end(), lcl_LessXPos() );
                    rAlignment.mfLabelDirection = (fDeltaY < 0) ? -1.0 : 1.0;
                }
                else
                {
                    rAlignment.meAlignment = LABEL_ALIGN_BOTTOM;
                    //choose most bottom positions
                    std::sort( aPosList.begin(), aPosList.end(), lcl_GreaterYPos() );
                    rAlignment.mfLabelDirection = (fDeltaX < 0) ? -1.0 : 1.0;
                }
                ScreenPosAndLogicPos aBestPos( aPosList[0] );
                fYStart = fYEnd = aBestPos.fLogicY;
                fZStart = fZEnd = aBestPos.fLogicZ;
                if( !m_pPosHelper->isMathematicalOrientationX() )
                    rAlignment.mfLabelDirection *= -1.0;
            }
        }//end 3D x axis
    }
    else if( m_nDimensionIndex==1 ) //y-axis
    {
        if( fCrossesOtherAxis < fMinX )
            fCrossesOtherAxis = fMinX;
        else if( fCrossesOtherAxis > fMaxX )
            fCrossesOtherAxis = fMaxX;
 
        fXStart = fXEnd = fCrossesOtherAxis;
        fYEnd=m_pPosHelper->getLogicMaxY();
 
        if(m_nDimension==3)
        {
            if( AxisHelper::isAxisPositioningEnabled() )
            {
                if( ::rtl::math::approxEqual( fXOther, fXStart) )
                    fZStart = fZEnd = fZOnZPlane;
                else
                    fZStart = fZEnd = fZOther;
            }
            else
            {
                rStart = getScreenPosition( fXStart, fYStart, fZStart );
                rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
 
                double fDeltaX = rEnd.getX() - rStart.getX();
                double fDeltaY = rEnd.getY() - rStart.getY();
 
                //only those points are candidates which are lying on exactly one wall as these are outer edges
                tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fXOnXPlane, fMinY, fZOther ), getScreenPosAndLogicPos( fXOther, fMinY, fZOnZPlane ) };
 
                if( fabs(fDeltaY) > fabs(fDeltaX)  )
                {
                    rAlignment.meAlignment = LABEL_ALIGN_LEFT;
                    //choose most left positions
                    std::sort( aPosList.begin(), aPosList.end(), lcl_LessXPos() );
                    rAlignment.mfLabelDirection = (fDeltaY < 0) ? -1.0 : 1.0;
                }
                else
                {
                    rAlignment.meAlignment = LABEL_ALIGN_BOTTOM;
                    //choose most bottom positions
                    std::sort( aPosList.begin(), aPosList.end(), lcl_GreaterYPos() );
                    rAlignment.mfLabelDirection = (fDeltaX < 0) ? -1.0 : 1.0;
                }
                ScreenPosAndLogicPos aBestPos( aPosList[0] );
                fXStart = fXEnd = aBestPos.fLogicX;
                fZStart = fZEnd = aBestPos.fLogicZ;
                if( !m_pPosHelper->isMathematicalOrientationY() )
                    rAlignment.mfLabelDirection *= -1.0;
            }
        }//end 3D y axis
    }
    else //z-axis
    {
        fZEnd = m_pPosHelper->getLogicMaxZ();
        if( AxisHelper::isAxisPositioningEnabled() )
        {
            if( !m_aAxisProperties.m_bSwapXAndY )
            {
                if( fCrossesOtherAxis < fMinY )
                    fCrossesOtherAxis = fMinY;
                else if( fCrossesOtherAxis > fMaxY )
                    fCrossesOtherAxis = fMaxY;
                fYStart = fYEnd = fCrossesOtherAxis;
 
                if( ::rtl::math::approxEqual( fYOther, fYStart) )
                    fXStart = fXEnd = fXOnXPlane;
                else
                    fXStart = fXEnd = fXOther;
            }
            else
            {
                if( fCrossesOtherAxis < fMinX )
                    fCrossesOtherAxis = fMinX;
                else if( fCrossesOtherAxis > fMaxX )
                    fCrossesOtherAxis = fMaxX;
                fXStart = fXEnd = fCrossesOtherAxis;
 
                if( ::rtl::math::approxEqual( fXOther, fXStart) )
                    fYStart = fYEnd = fYOnYPlane;
                else
                    fYStart = fYEnd = fYOther;
            }
        }
        else
        {
            if( !m_pPosHelper->isSwapXAndY() )
            {
                fXStart = fXEnd = m_pPosHelper->isMathematicalOrientationX() ? m_pPosHelper->getLogicMaxX() : m_pPosHelper->getLogicMinX();
                fYStart = fYEnd = m_pPosHelper->isMathematicalOrientationY() ? m_pPosHelper->getLogicMinY() : m_pPosHelper->getLogicMaxY();
            }
            else
            {
                fXStart = fXEnd = m_pPosHelper->isMathematicalOrientationX() ? m_pPosHelper->getLogicMinX() : m_pPosHelper->getLogicMaxX();
                fYStart = fYEnd = m_pPosHelper->isMathematicalOrientationY() ? m_pPosHelper->getLogicMaxY() : m_pPosHelper->getLogicMinY();
            }
 
            if(m_nDimension==3)
            {
                rStart = getScreenPosition( fXStart, fYStart, fZStart );
                rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
 
                double fDeltaX = rEnd.getX() - rStart.getX();
 
                //only those points are candidates which are lying on exactly one wall as these are outer edges
                tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fXOther, fYOnYPlane, fMinZ ), getScreenPosAndLogicPos( fXOnXPlane, fYOther, fMinZ ) };
 
                std::sort( aPosList.begin(), aPosList.end(), lcl_GreaterYPos() );
                ScreenPosAndLogicPos aBestPos( aPosList[0] );
                ScreenPosAndLogicPos aNotSoGoodPos( aPosList[1] );
 
                //choose most bottom positions
                if( fDeltaX != 0.0 ) // prefer left-right alignments
                {
                    if( aBestPos.aScreenPos.getX() > aNotSoGoodPos.aScreenPos.getX() )
                        rAlignment.meAlignment = LABEL_ALIGN_RIGHT;
                    else
                         rAlignment.meAlignment = LABEL_ALIGN_LEFT;
                }
                else
                {
                    if( aBestPos.aScreenPos.getY() > aNotSoGoodPos.aScreenPos.getY() )
                        rAlignment.meAlignment = LABEL_ALIGN_BOTTOM;
                    else
                        rAlignment.meAlignment = LABEL_ALIGN_TOP;
                }
 
                rAlignment.mfLabelDirection = (fDeltaX < 0) ? -1.0 : 1.0;
                if( !m_pPosHelper->isMathematicalOrientationZ() )
                    rAlignment.mfLabelDirection *= -1.0;
 
                fXStart = fXEnd = aBestPos.fLogicX;
                fYStart = fYEnd = aBestPos.fLogicY;
            }
        }//end 3D z axis
    }
 
    rStart = getScreenPosition( fXStart, fYStart, fZStart );
    rEnd = getScreenPosition( fXEnd, fYEnd, fZEnd );
 
    if(m_nDimension==3 && !AxisHelper::isAxisPositioningEnabled() )
        rAlignment.mfInnerTickDirection = rAlignment.mfLabelDirection;//to behave like before
 
    if(!(m_nDimension==3 && AxisHelper::isAxisPositioningEnabled()) )
        return;
 
    double fDeltaX = rEnd.getX() - rStart.getX();
    double fDeltaY = rEnd.getY() - rStart.getY();
 
    if( m_nDimensionIndex==2 )
    {
        if( m_eLeftWallPos != CuboidPlanePosition_Left )
        {
            rAlignment.mfLabelDirection *= -1.0;
            rAlignment.mfInnerTickDirection *= -1.0;
        }
 
        rAlignment.meAlignment =
            (rAlignment.mfLabelDirection < 0) ?
                LABEL_ALIGN_LEFT :  LABEL_ALIGN_RIGHT;
 
        if( ( fDeltaY<0 && m_aScale.Orientation == chart2::AxisOrientation_REVERSE ) ||
            ( fDeltaY>0 && m_aScale.Orientation == chart2::AxisOrientation_MATHEMATICAL ) )
            rAlignment.meAlignment =
                (rAlignment.meAlignment == LABEL_ALIGN_RIGHT) ?
                    LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
    }
    else if( fabs(fDeltaY) > fabs(fDeltaX) )
    {
        if( m_eBackWallPos != CuboidPlanePosition_Back )
        {
            rAlignment.mfLabelDirection *= -1.0;
            rAlignment.mfInnerTickDirection *= -1.0;
        }
 
        rAlignment.meAlignment =
            (rAlignment.mfLabelDirection < 0) ?
                LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
 
        if( ( fDeltaY<0 && m_aScale.Orientation == chart2::AxisOrientation_REVERSE ) ||
            ( fDeltaY>0 && m_aScale.Orientation == chart2::AxisOrientation_MATHEMATICAL ) )
            rAlignment.meAlignment =
                (rAlignment.meAlignment == LABEL_ALIGN_RIGHT) ?
                    LABEL_ALIGN_LEFT :  LABEL_ALIGN_RIGHT;
    }
    else
    {
        if( m_eBackWallPos != CuboidPlanePosition_Back )
        {
            rAlignment.mfLabelDirection *= -1.0;
            rAlignment.mfInnerTickDirection *= -1.0;
        }
 
        rAlignment.meAlignment =
            (rAlignment.mfLabelDirection < 0) ?
                LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
 
        if( ( fDeltaX>0 && m_aScale.Orientation == chart2::AxisOrientation_REVERSE ) ||
            ( fDeltaX<0 && m_aScale.Orientation == chart2::AxisOrientation_MATHEMATICAL ) )
            rAlignment.meAlignment =
                (rAlignment.meAlignment == LABEL_ALIGN_TOP) ?
                    LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP;
    }
}
 
TickFactory* VCartesianAxis::createTickFactory()
{
    return createTickFactory2D();
}
 
TickFactory2D* VCartesianAxis::createTickFactory2D()
{
    AxisLabelAlignment aLabelAlign = m_aAxisProperties.maLabelAlignment;
    B2DVector aStart, aEnd;
    get2DAxisMainLine(aStart, aEnd, aLabelAlign, getAxisIntersectionValue());
 
    B2DVector aLabelLineStart, aLabelLineEnd;
    get2DAxisMainLine(aLabelLineStart, aLabelLineEnd, aLabelAlign, getLabelLineIntersectionValue());
    m_aAxisProperties.maLabelAlignment = aLabelAlign;
 
    return new TickFactory2D( m_aScale, m_aIncrement, aStart, aEnd, aLabelLineStart-aStart );
}
 
static void lcl_hideIdenticalScreenValues( TickIter& rTickIter )
{
    TickInfo* pPrevTickInfo = rTickIter.firstInfo();
    if (!pPrevTickInfo)
        return;
 
    pPrevTickInfo->bPaintIt = true;
    for( TickInfo* pTickInfo = rTickIter.nextInfo(); pTickInfo; pTickInfo = rTickIter.nextInfo())
    {
        pTickInfo->bPaintIt = (pTickInfo->aTickScreenPosition != pPrevTickInfo->aTickScreenPosition);
        pPrevTickInfo = pTickInfo;
    }
}
 
//'hide' tickmarks with identical screen values in aAllTickInfos
void VCartesianAxis::hideIdenticalScreenValues( TickInfoArraysType& rTickInfos ) const
{
    if( isComplexCategoryAxis() || isDateAxis() )
    {
        sal_Int32 nCount = rTickInfos.size();
        for( sal_Int32 nN=0; nN<nCount; nN++ )
        {
            PureTickIter aTickIter( rTickInfos[nN] );
            lcl_hideIdenticalScreenValues( aTickIter );
        }
    }
    else
    {
        EquidistantTickIter aTickIter( rTickInfos, m_aIncrement, -1 );
        lcl_hideIdenticalScreenValues( aTickIter );
    }
}
 
sal_Int32 VCartesianAxis::estimateMaximumAutoMainIncrementCount()
{
    sal_Int32 nRet = 10;
 
    if( m_nMaximumTextWidthSoFar==0 && m_nMaximumTextHeightSoFar==0 )
        return nRet;
 
    B2DVector aStart, aEnd;
    AxisLabelAlignment aLabelAlign = m_aAxisProperties.maLabelAlignment;
    get2DAxisMainLine(aStart, aEnd, aLabelAlign, getAxisIntersectionValue());
    m_aAxisProperties.maLabelAlignment = aLabelAlign;
 
    sal_Int32 nMaxHeight = static_cast<sal_Int32>(fabs(aEnd.getY()-aStart.getY()));
    sal_Int32 nMaxWidth = static_cast<sal_Int32>(fabs(aEnd.getX()-aStart.getX()));
 
    sal_Int32 nTotalAvailable = nMaxHeight;
    sal_Int32 nSingleNeeded = m_nMaximumTextHeightSoFar;
    sal_Int32 nMaxSameLabel = 0;
 
    // tdf#48041: do not duplicate the value labels because of rounding
    if (m_aAxisProperties.m_nAxisType != css::chart2::AxisType::DATE)
    {
        FixedNumberFormatter aFixedNumberFormatterTest(m_xNumberFormatsSupplier, m_aAxisLabelProperties.m_nNumberFormatKey);
        OUString sPreviousValueLabel;
        sal_Int32 nSameLabel = 0;
        for (auto const & nLabel: m_aAllTickInfos[0])
        {
            Color nColor = COL_AUTO;
            bool bHasColor = false;
            OUString sValueLabel = aFixedNumberFormatterTest.getFormattedString(nLabel.fScaledTickValue, nColor, bHasColor);
            if (sValueLabel == sPreviousValueLabel)
            {
                nSameLabel++;
                if (nSameLabel > nMaxSameLabel)
                    nMaxSameLabel = nSameLabel;
            }
            else
                nSameLabel = 0;
            sPreviousValueLabel = sValueLabel;
        }
    }
    //for horizontal axis:
    if( (m_nDimensionIndex == 0 && !m_aAxisProperties.m_bSwapXAndY)
        || (m_nDimensionIndex == 1 && m_aAxisProperties.m_bSwapXAndY) )
    {
        nTotalAvailable = nMaxWidth;
        nSingleNeeded = m_nMaximumTextWidthSoFar;
    }
 
    if( nSingleNeeded>0 )
        nRet = nTotalAvailable/nSingleNeeded;
 
    if ( nMaxSameLabel > 0 )
    {
        sal_Int32 nRetNoSameLabel = m_aAllTickInfos[0].size() / (nMaxSameLabel + 1);
        if ( nRet > nRetNoSameLabel )
           nRet = nRetNoSameLabel;
    }
 
    return nRet;
}
 
void VCartesianAxis::doStaggeringOfLabels( const AxisLabelProperties& rAxisLabelProperties, TickFactory2D const * pTickFactory2D )
{
    if( !pTickFactory2D )
        return;
 
    if( isComplexCategoryAxis() )
    {
        sal_Int32 nTextLevelCount = getTextLevelCount();
        B2DVector aCumulatedLabelsDistance(0,0);
        for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
        {
            std::unique_ptr<TickIter> apTickIter(createLabelTickIterator(nTextLevel));
            if (apTickIter)
            {
                double fRotationAngleDegree = m_aAxisLabelProperties.m_fRotationAngleDegree;
                if( nTextLevel>0 )
                {
                    lcl_shiftLabels(*apTickIter, aCumulatedLabelsDistance);
                    //multilevel labels: 0 or 90 by default
                    if( m_aAxisProperties.m_bSwapXAndY )
                        fRotationAngleDegree = 90.0;
                    else
                        fRotationAngleDegree = 0.0;
                }
                aCumulatedLabelsDistance += lcl_getLabelsDistance(
                    *apTickIter, pTickFactory2D->getDistanceAxisTickToText(m_aAxisProperties),
                    fRotationAngleDegree);
            }
        }
    }
    else if (rAxisLabelProperties.isStaggered())
    {
        if( !m_aAllTickInfos.empty() )
        {
            LabelIterator aInnerIter( m_aAllTickInfos[0], rAxisLabelProperties.m_eStaggering, true );
            LabelIterator aOuterIter( m_aAllTickInfos[0], rAxisLabelProperties.m_eStaggering, false );
 
            lcl_shiftLabels( aOuterIter
                , lcl_getLabelsDistance( aInnerIter
                    , pTickFactory2D->getDistanceAxisTickToText( m_aAxisProperties ), 0.0 ) );
        }
    }
}
 
void VCartesianAxis::createDataTableShape(std::unique_ptr<TickFactory2D> const& rpTickFactory2D)
{
    // Check if we can create the data table shape
    // Data table view and m_bDisplayDataTable must be true
    if (!m_pDataTableView || !m_aAxisProperties.m_bDisplayDataTable)
        return;
 
    m_pDataTableView->initializeShapes(m_xDataTableTarget);
    basegfx::B2DVector aStart = rpTickFactory2D->getXaxisStartPos();
    basegfx::B2DVector aEnd = rpTickFactory2D->getXaxisEndPos();
 
    rpTickFactory2D->updateScreenValues(m_aAllTickInfos);
 
    sal_Int32 nDistance = -1;
 
    std::unique_ptr<TickIter> apTickIter(createLabelTickIterator(0));
    if (apTickIter)
    {
        nDistance = TickFactory2D::getTickScreenDistance(*apTickIter);
        if (getTextLevelCount() > 1)
            nDistance *= 2;
    }
 
    if (nDistance <= 0)
    {
        // we only have one data series so we have no TickMarks, therefore calculate and use the table size
        auto rDelta = aEnd - aStart;
        nDistance = basegfx::fround(rDelta.getX());
    }
 
    if (nDistance > 0)
    {
        m_pDataTableView->createShapes(aStart, aEnd, nDistance);
    }
}
 
void VCartesianAxis::createLabels()
{
    if( !prepareShapeCreation() )
        return;
 
    std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
 
    createDataTableShape(apTickFactory2D);
 
    //create labels
    if (!m_aAxisProperties.m_bDisplayLabels)
        return;
 
    TickFactory2D* pTickFactory2D = apTickFactory2D.get();
 
    //get the transformed screen values for all tickmarks in aAllTickInfos
    pTickFactory2D->updateScreenValues( m_aAllTickInfos );
    //'hide' tickmarks with identical screen values in aAllTickInfos
    hideIdenticalScreenValues( m_aAllTickInfos );
 
    removeTextShapesFromTicks();
 
    //create tick mark text shapes
    sal_Int32 nTextLevelCount = getTextLevelCount();
    sal_Int32 nScreenDistanceBetweenTicks = -1;
    for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
    {
        std::unique_ptr< TickIter > apTickIter(createLabelTickIterator( nTextLevel ));
        if(apTickIter)
        {
            if(nTextLevel==0)
            {
                nScreenDistanceBetweenTicks = TickFactory2D::getTickScreenDistance(*apTickIter);
                if( nTextLevelCount>1 )
                    nScreenDistanceBetweenTicks*=2; //the above used tick iter does contain also the sub ticks -> thus the given distance is only the half
            }
 
            AxisLabelProperties aComplexProps(m_aAxisLabelProperties);
            if( m_aAxisProperties.m_bComplexCategories )
            {
                aComplexProps.m_bLineBreakAllowed = true;
                aComplexProps.m_bOverlapAllowed = aComplexProps.m_fRotationAngleDegree != 0.0;
                if( nTextLevel > 0 )
                {
                    //multilevel labels: 0 or 90 by default
                    if( m_aAxisProperties.m_bSwapXAndY )
                        aComplexProps.m_fRotationAngleDegree = 90.0;
                    else
                        aComplexProps.m_fRotationAngleDegree = 0.0;
                }
            }
            AxisLabelProperties& rAxisLabelProperties =  m_aAxisProperties.m_bComplexCategories ? aComplexProps : m_aAxisLabelProperties;
            while (!createTextShapes(m_xTextTarget, *apTickIter, rAxisLabelProperties,
                                     pTickFactory2D, nScreenDistanceBetweenTicks))
            {
            };
        }
    }
    doStaggeringOfLabels( m_aAxisLabelProperties, pTickFactory2D );
 
    if (m_pDataTableView)
    {
        sal_Int32 x = m_xTextTarget->getPosition().X;
        sal_Int32 y = m_xTextTarget->getPosition().Y;
        sal_Int32 height = m_xTextTarget->getSize().Height;
        m_pDataTableView->changePosition(x, y + height);
    }
}
 
void VCartesianAxis::createMaximumLabels()
{
    m_bRecordMaximumTextSize = true;
    const comphelper::ScopeGuard aGuard([this]() { m_bRecordMaximumTextSize = false; });
 
    if( !prepareShapeCreation() )
        return;
 
    //create labels
    if (!m_aAxisProperties.m_bDisplayLabels)
        return;
 
    std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
    TickFactory2D* pTickFactory2D = apTickFactory2D.get();
 
    //get the transformed screen values for all tickmarks in aAllTickInfos
    pTickFactory2D->updateScreenValues( m_aAllTickInfos );
 
    //create tick mark text shapes
    //@todo: iterate through all tick depth which should be labeled
 
    AxisLabelProperties aAxisLabelProperties( m_aAxisLabelProperties );
    if( isAutoStaggeringOfLabelsAllowed( aAxisLabelProperties, pTickFactory2D->isHorizontalAxis(), pTickFactory2D->isVerticalAxis() ) )
        aAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
 
    aAxisLabelProperties.m_bOverlapAllowed = true;
    aAxisLabelProperties.m_bLineBreakAllowed = false;
    sal_Int32 nTextLevelCount = getTextLevelCount();
    for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
    {
        std::unique_ptr< TickIter > apTickIter(createMaximumLabelTickIterator( nTextLevel ));
        if(apTickIter)
        {
            while (!createTextShapes(m_xTextTarget, *apTickIter, aAxisLabelProperties,
                                     pTickFactory2D, -1))
            {
            };
        }
    }
    doStaggeringOfLabels( aAxisLabelProperties, pTickFactory2D );
}
 
void VCartesianAxis::updatePositions()
{
    //update positions of labels
    if (!m_aAxisProperties.m_bDisplayLabels)
        return;
 
    std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
    TickFactory2D* pTickFactory2D = apTickFactory2D.get();
 
    //update positions of all existing text shapes
    pTickFactory2D->updateScreenValues( m_aAllTickInfos );
 
    sal_Int32 nDepth=0;
    for (auto const& tickInfos : m_aAllTickInfos)
    {
        for (auto const& tickInfo : tickInfos)
        {
            const rtl::Reference<SvxShapeText> & xShape2DText(tickInfo.xTextShape);
            if( xShape2DText.is() )
            {
                B2DVector aTextToTickDistance( pTickFactory2D->getDistanceAxisTickToText( m_aAxisProperties, true ) );
                B2DVector aTickScreenPos2D(tickInfo.aTickScreenPosition);
                aTickScreenPos2D += aTextToTickDistance;
                awt::Point aAnchorScreenPosition2D(
                    static_cast<sal_Int32>(aTickScreenPos2D.getX())
                    ,static_cast<sal_Int32>(aTickScreenPos2D.getY()));
 
                double fRotationAngleDegree = m_aAxisLabelProperties.m_fRotationAngleDegree;
                if( nDepth > 0 )
                {
                    //multilevel labels: 0 or 90 by default
                    if( pTickFactory2D->isHorizontalAxis() )
                        fRotationAngleDegree = 0.0;
                    else
                        fRotationAngleDegree = 90;
                }
 
                // #i78696# use mathematically correct rotation now
                const double fRotationAnglePi(-basegfx::deg2rad(fRotationAngleDegree));
                uno::Any aATransformation = ShapeFactory::makeTransformation(aAnchorScreenPosition2D, fRotationAnglePi);
 
                //set new position
                try
                {
                    xShape2DText->SvxShape::setPropertyValue( u"Transformation"_ustr, aATransformation );
                }
                catch( const uno::Exception& )
                {
                    TOOLS_WARN_EXCEPTION("chart2", "" );
                }
 
                //correctPositionForRotation
                LabelPositionHelper::correctPositionForRotation( xShape2DText
                    , m_aAxisProperties.maLabelAlignment.meAlignment, fRotationAngleDegree, m_aAxisProperties.m_bComplexCategories );
            }
        }
        ++nDepth;
    }
 
    doStaggeringOfLabels( m_aAxisLabelProperties, pTickFactory2D );
}
 
void VCartesianAxis::createTickMarkLineShapes( TickInfoArrayType& rTickInfos, const TickmarkProperties& rTickmarkProperties, TickFactory2D const & rTickFactory2D, bool bOnlyAtLabels )
{
    sal_Int32 nPointCount = rTickInfos.size();
    drawing::PointSequenceSequence aPoints(2*nPointCount);
 
    sal_Int32 nN = 0;
    for (auto const& tickInfo : rTickInfos)
    {
        if( !tickInfo.bPaintIt )
            continue;
 
        bool bTicksAtLabels = ( m_aAxisProperties.m_eTickmarkPos != css::chart::ChartAxisMarkPosition_AT_AXIS );
        double fInnerDirectionSign = m_aAxisProperties.maLabelAlignment.mfInnerTickDirection;
        if( bTicksAtLabels && m_aAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END )
            fInnerDirectionSign *= -1.0;
        bTicksAtLabels = bTicksAtLabels || bOnlyAtLabels;
        //add ticks at labels:
        rTickFactory2D.addPointSequenceForTickLine( aPoints, nN++, tickInfo.fScaledTickValue
            , fInnerDirectionSign , rTickmarkProperties, bTicksAtLabels );
        //add ticks at axis (without labels):
        if( !bOnlyAtLabels && m_aAxisProperties.m_eTickmarkPos == css::chart::ChartAxisMarkPosition_AT_LABELS_AND_AXIS )
            rTickFactory2D.addPointSequenceForTickLine( aPoints, nN++, tickInfo.fScaledTickValue
                , m_aAxisProperties.maLabelAlignment.mfInnerTickDirection, rTickmarkProperties, !bTicksAtLabels );
    }
    aPoints.realloc(nN);
    ShapeFactory::createLine2D( m_xGroupShape_Shapes, aPoints
                                , &rTickmarkProperties.aLineProperties );
}
 
void VCartesianAxis::createShapes()
{
    if( !prepareShapeCreation() )
        return;
 
    //create line shapes
    if(m_nDimension==2)
    {
        std::unique_ptr<TickFactory2D> apTickFactory2D(createTickFactory2D()); // throws on failure
        TickFactory2D* pTickFactory2D = apTickFactory2D.get();
 
        //create extra long ticks to separate complex categories (create them only there where the labels are)
        if( isComplexCategoryAxis() )
        {
            TickInfoArraysType aComplexTickInfos;
            createAllTickInfosFromComplexCategories( aComplexTickInfos, true );
            pTickFactory2D->updateScreenValues( aComplexTickInfos );
            hideIdenticalScreenValues( aComplexTickInfos );
 
            std::vector<TickmarkProperties> aTickmarkPropertiesList;
            static const bool bIncludeSpaceBetweenTickAndText = false;
            sal_Int32 nOffset = static_cast<sal_Int32>(pTickFactory2D->getDistanceAxisTickToText( m_aAxisProperties, false, bIncludeSpaceBetweenTickAndText ).getLength());
            sal_Int32 nTextLevelCount = getTextLevelCount();
            for( sal_Int32 nTextLevel=0; nTextLevel<nTextLevelCount; nTextLevel++ )
            {
                std::unique_ptr< TickIter > apTickIter(createLabelTickIterator( nTextLevel ));
                if( apTickIter )
                {
                    double fRotationAngleDegree = m_aAxisLabelProperties.m_fRotationAngleDegree;
                    if( nTextLevel > 0 )
                    {
                        //Multi-level Labels: default to 0 or 90
                        if( m_aAxisProperties.m_bSwapXAndY )
                            fRotationAngleDegree = 90.0;
                        else
                            fRotationAngleDegree = 0.0;
                    }
                    B2DVector aLabelsDistance(lcl_getLabelsDistance(
                        *apTickIter, pTickFactory2D->getDistanceAxisTickToText(m_aAxisProperties),
                        fRotationAngleDegree));
                    sal_Int32 nCurrentLength = static_cast<sal_Int32>(aLabelsDistance.getLength());
                    aTickmarkPropertiesList.push_back( m_aAxisProperties.makeTickmarkPropertiesForComplexCategories( nOffset + nCurrentLength, 0 ) );
                    nOffset += nCurrentLength;
                }
            }
 
            sal_Int32 nTickmarkPropertiesCount = aTickmarkPropertiesList.size();
            TickInfoArraysType::iterator aDepthIter             = aComplexTickInfos.begin();
            const TickInfoArraysType::const_iterator aDepthEnd  = aComplexTickInfos.end();
            for( sal_Int32 nDepth=0; aDepthIter != aDepthEnd && nDepth < nTickmarkPropertiesCount; ++aDepthIter, nDepth++ )
            {
                if(nDepth==0 && !m_aAxisProperties.m_nMajorTickmarks)
                    continue;
                createTickMarkLineShapes( *aDepthIter, aTickmarkPropertiesList[nDepth], *pTickFactory2D, true /*bOnlyAtLabels*/ );
            }
        }
        //create normal ticks for major and minor intervals
        {
            TickInfoArraysType aUnshiftedTickInfos;
            if( m_aScale.m_bShiftedCategoryPosition )// if m_bShiftedCategoryPosition==true the tickmarks in m_aAllTickInfos are shifted
            {
                pTickFactory2D->getAllTicks( aUnshiftedTickInfos );
                pTickFactory2D->updateScreenValues( aUnshiftedTickInfos );
                hideIdenticalScreenValues( aUnshiftedTickInfos );
            }
            TickInfoArraysType& rAllTickInfos = m_aScale.m_bShiftedCategoryPosition ? aUnshiftedTickInfos : m_aAllTickInfos;
 
            if (rAllTickInfos.empty())
                return;
 
            sal_Int32 nDepth = 0;
            sal_Int32 nTickmarkPropertiesCount = m_aAxisProperties.m_aTickmarkPropertiesList.size();
            for( auto& rTickInfos : rAllTickInfos )
            {
                if (nDepth == nTickmarkPropertiesCount)
                    break;
 
                createTickMarkLineShapes( rTickInfos, m_aAxisProperties.m_aTickmarkPropertiesList[nDepth], *pTickFactory2D, false /*bOnlyAtLabels*/ );
                nDepth++;
            }
        }
        //create axis main lines
        //it serves also as the handle shape for the axis selection
        {
            drawing::PointSequenceSequence aPoints(1);
            apTickFactory2D->createPointSequenceForAxisMainLine( aPoints );
            rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
                    m_xGroupShape_Shapes, aPoints
                    , &m_aAxisProperties.m_aLineProperties );
            //because of this name this line will be used for marking the axis
            ::chart::ShapeFactory::setShapeName( xShape, u"MarkHandles"_ustr );
        }
        //create an additional line at NULL
        if( !AxisHelper::isAxisPositioningEnabled() )
        {
            double fExtraLineCrossesOtherAxis = getExtraLineIntersectionValue();
            if (!std::isnan(fExtraLineCrossesOtherAxis))
            {
                B2DVector aStart, aEnd;
                AxisLabelAlignment aLabelAlign = m_aAxisProperties.maLabelAlignment;
                get2DAxisMainLine(aStart, aEnd, aLabelAlign, fExtraLineCrossesOtherAxis);
                m_aAxisProperties.maLabelAlignment = aLabelAlign;
                drawing::PointSequenceSequence aPoints{{
                        {static_cast<sal_Int32>(aStart.getX()), static_cast<sal_Int32>(aStart.getY())},
                        {static_cast<sal_Int32>(aEnd.getX()), static_cast<sal_Int32>(aEnd.getY())} }};
                ShapeFactory::createLine2D(
                        m_xGroupShape_Shapes, aPoints, &m_aAxisProperties.m_aLineProperties );
            }
        }
    }
 
    createLabels();
}
 
void VCartesianAxis::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)
{
    if (!m_aAxisProperties.m_bDisplayDataTable)
        return;
 
    m_pDataTableView.reset(new DataTableView(xChartDoc, m_aAxisProperties.m_xDataTableModel, rComponentContext, m_aAxisProperties.m_bDataTableAlignAxisValuesWithColumns));
    m_pDataTableView->initializeValues(rSeriesPlotterList);
    m_xNumberFormatsSupplier = xNumberFormatsSupplier;
}
 
 
} //namespace chart
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'normalize' is required to be utilized.

V560 A part of conditional expression is always true: m_aAxisProperties.m_bSwapXAndY.