Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  VCartesianAxis.cxx   Sprache: C

 
/* -*- 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, truefalse );

    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&&nbsp;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&&nbsp;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())
    {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=91 G=91

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge