diff options
Diffstat (limited to 'chart2/source/view/axes')
35 files changed, 8607 insertions, 0 deletions
diff --git a/chart2/source/view/axes/DateHelper.cxx b/chart2/source/view/axes/DateHelper.cxx new file mode 100644 index 000000000..4c4a96dce --- /dev/null +++ b/chart2/source/view/axes/DateHelper.cxx @@ -0,0 +1,93 @@ +/* -*- 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 <DateHelper.hxx> +#include <rtl/math.hxx> +#include <com/sun/star/chart/TimeUnit.hpp> + +namespace chart +{ +using namespace ::com::sun::star; + +bool DateHelper::IsInSameYear( const Date& rD1, const Date& rD2 ) +{ + return rD1.GetYear() == rD2.GetYear(); +} + +bool DateHelper::IsInSameMonth( const Date& rD1, const Date& rD2 ) +{ + return (rD1.GetYear() == rD2.GetYear()) + && (rD1.GetMonth() == rD2.GetMonth()); +} + +Date DateHelper::GetDateSomeMonthsAway( const Date& rD, sal_Int32 nMonthDistance ) +{ + Date aRet(rD); + aRet.AddMonths( nMonthDistance ); + return aRet; +} + +Date DateHelper::GetDateSomeYearsAway( const Date& rD, sal_Int32 nYearDistance ) +{ + Date aRet(rD); + aRet.AddYears( static_cast<sal_Int16>(nYearDistance) ); + return aRet; +} + +bool DateHelper::IsLessThanOneMonthAway( const Date& rD1, const Date& rD2 ) +{ + Date aDMin( DateHelper::GetDateSomeMonthsAway( rD1, -1 ) ); + Date aDMax( DateHelper::GetDateSomeMonthsAway( rD1, 1 ) ); + + return rD2 > aDMin && rD2 < aDMax; +} + +bool DateHelper::IsLessThanOneYearAway( const Date& rD1, const Date& rD2 ) +{ + Date aDMin( DateHelper::GetDateSomeYearsAway( rD1, -1 ) ); + Date aDMax( DateHelper::GetDateSomeYearsAway( rD1, 1 ) ); + + return rD2 > aDMin && rD2 < aDMax; +} + +double DateHelper::RasterizeDateValue( double fValue, const Date& rNullDate, tools::Long TimeResolution ) +{ + if (std::isnan(fValue)) + return fValue; + + Date aDate(rNullDate); aDate.AddDays(::rtl::math::approxFloor(fValue)); + switch(TimeResolution) + { + case css::chart::TimeUnit::DAY: + break; + case css::chart::TimeUnit::YEAR: + aDate.SetMonth(1); + aDate.SetDay(1); + break; + case css::chart::TimeUnit::MONTH: + default: + aDate.SetDay(1); + break; + } + return aDate - rNullDate; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/DateScaling.cxx b/chart2/source/view/axes/DateScaling.cxx new file mode 100644 index 000000000..15cb59635 --- /dev/null +++ b/chart2/source/view/axes/DateScaling.cxx @@ -0,0 +1,205 @@ +/* -*- 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 "DateScaling.hxx" +#include <com/sun/star/chart/TimeUnit.hpp> +#include <rtl/math.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <limits> + +namespace +{ + +constexpr OUStringLiteral lcl_aServiceName_DateScaling = u"com.sun.star.chart2.DateScaling"; +constexpr OUStringLiteral lcl_aServiceName_InverseDateScaling + = u"com.sun.star.chart2.InverseDateScaling"; + +const double lcl_fNumberOfMonths = 12.0;//todo: this needs to be offered by basic tools Date class if it should be more generic +} + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::chart::TimeUnit::DAY; +using ::com::sun::star::chart::TimeUnit::MONTH; +using ::com::sun::star::chart::TimeUnit::YEAR; + +DateScaling::DateScaling( const Date& rNullDate, sal_Int32 nTimeUnit, bool bShifted ) + : m_aNullDate( rNullDate ) + , m_nTimeUnit( nTimeUnit ) + , m_bShifted( bShifted ) +{ +} + +DateScaling::~DateScaling() +{ +} + +double SAL_CALL DateScaling::doScaling( double value ) +{ + double fResult(value); + if( std::isnan( value ) || std::isinf( value ) ) + return std::numeric_limits<double>::quiet_NaN(); + switch( m_nTimeUnit ) + { + case DAY: + fResult = value; + if(m_bShifted) + fResult+=0.5; + break; + case YEAR: + case MONTH: + default: + { + Date aDate(m_aNullDate); + aDate.AddDays(::rtl::math::approxFloor(value)); + fResult = aDate.GetYear(); + fResult *= lcl_fNumberOfMonths;//assuming equal count of months in each year + fResult += aDate.GetMonth(); + + double fDayOfMonth = aDate.GetDay(); + fDayOfMonth -= 1.0; + double fDaysInMonth = aDate.GetDaysInMonth(); + fResult += fDayOfMonth/fDaysInMonth; + if(m_bShifted) + { + if( m_nTimeUnit==YEAR ) + fResult += 0.5*lcl_fNumberOfMonths; + else + fResult += 0.5; + } + break; + } + } + return fResult; +} + +uno::Reference< XScaling > SAL_CALL DateScaling::getInverseScaling() +{ + return new InverseDateScaling( m_aNullDate, m_nTimeUnit, m_bShifted ); +} + +OUString SAL_CALL DateScaling::getServiceName() +{ + return lcl_aServiceName_DateScaling; +} + +OUString SAL_CALL DateScaling::getImplementationName() +{ + return lcl_aServiceName_DateScaling; +} + +sal_Bool SAL_CALL DateScaling::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL DateScaling::getSupportedServiceNames() +{ + return { lcl_aServiceName_DateScaling }; +} + +InverseDateScaling::InverseDateScaling( const Date& rNullDate, sal_Int32 nTimeUnit, bool bShifted ) + : m_aNullDate( rNullDate ) + , m_nTimeUnit( nTimeUnit ) + , m_bShifted( bShifted ) +{ +} + +InverseDateScaling::~InverseDateScaling() +{ +} + +double SAL_CALL InverseDateScaling::doScaling( double value ) +{ + double fResult(value); + if( std::isnan( value ) || std::isinf( value ) ) + return std::numeric_limits<double>::quiet_NaN(); + else + { + switch( m_nTimeUnit ) + { + case DAY: + if(m_bShifted) + value -= 0.5; + fResult = value; + break; + case YEAR: + case MONTH: + default: + //Date aDate(m_aNullDate); + if(m_bShifted) + { + if( m_nTimeUnit==YEAR ) + value -= 0.5*lcl_fNumberOfMonths; + else + value -= 0.5; + } + Date aDate( Date::EMPTY ); + double fYear = ::rtl::math::approxFloor(value/lcl_fNumberOfMonths); + double fMonth = ::rtl::math::approxFloor(value-(fYear*lcl_fNumberOfMonths)); + if( fMonth==0.0 ) + { + fYear--; + fMonth=12.0; + } + aDate.SetYear( static_cast<sal_uInt16>(fYear) ); + aDate.SetMonth( static_cast<sal_uInt16>(fMonth) ); + aDate.SetDay( 1 ); + double fMonthCount = (fYear*lcl_fNumberOfMonths)+fMonth; + double fDay = (value-fMonthCount)*aDate.GetDaysInMonth(); + fDay += 1.0; + aDate.SetDay( static_cast<sal_uInt16>(::rtl::math::round(fDay)) ); + fResult = aDate - m_aNullDate; + break; + } + } + return fResult; +} + +uno::Reference< XScaling > SAL_CALL InverseDateScaling::getInverseScaling() +{ + return new DateScaling( m_aNullDate, m_nTimeUnit, m_bShifted ); +} + +OUString SAL_CALL InverseDateScaling::getServiceName() +{ + return lcl_aServiceName_InverseDateScaling; +} + +OUString SAL_CALL InverseDateScaling::getImplementationName() +{ + return lcl_aServiceName_InverseDateScaling; +} + +sal_Bool SAL_CALL InverseDateScaling::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL InverseDateScaling::getSupportedServiceNames() +{ + return { lcl_aServiceName_InverseDateScaling }; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/DateScaling.hxx b/chart2/source/view/axes/DateScaling.hxx new file mode 100644 index 000000000..29b7a139a --- /dev/null +++ b/chart2/source/view/axes/DateScaling.hxx @@ -0,0 +1,95 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/chart2/XScaling.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <cppuhelper/implbase.hxx> +#include <tools/date.hxx> + +namespace chart +{ + +class DateScaling : + public ::cppu::WeakImplHelper< + css::chart2::XScaling, + css::lang::XServiceName, + css::lang::XServiceInfo + > +{ +public: + DateScaling( const Date& rNullDate, sal_Int32 nTimeUnit, bool bShifted ); + virtual ~DateScaling() override; + + /// declare XServiceInfo methods + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // ____ XScaling ____ + virtual double SAL_CALL doScaling( double value ) override; + + virtual css::uno::Reference< + css::chart2::XScaling > SAL_CALL + getInverseScaling() override; + + // ____ XServiceName ____ + virtual OUString SAL_CALL getServiceName() override; + +private: + const Date m_aNullDate; + const sal_Int32 m_nTimeUnit; + const bool m_bShifted; +}; + +class InverseDateScaling : + public ::cppu::WeakImplHelper< + css::chart2::XScaling, + css::lang::XServiceName, + css::lang::XServiceInfo + > +{ +public: + InverseDateScaling( const Date& rNullDate, sal_Int32 nTimeUnit, bool bShifted ); + virtual ~InverseDateScaling() override; + + /// declare XServiceInfo methods + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // ____ XScaling ____ + virtual double SAL_CALL doScaling( double value ) override; + + virtual css::uno::Reference< css::chart2::XScaling > SAL_CALL + getInverseScaling() override; + + // ____ XServiceName ____ + virtual OUString SAL_CALL getServiceName() override; + +private: + const Date m_aNullDate; + const sal_Int32 m_nTimeUnit; + const bool m_bShifted; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/MinimumAndMaximumSupplier.cxx b/chart2/source/view/axes/MinimumAndMaximumSupplier.cxx new file mode 100644 index 000000000..eaf5c4347 --- /dev/null +++ b/chart2/source/view/axes/MinimumAndMaximumSupplier.cxx @@ -0,0 +1,203 @@ +/* -*- 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 <MinimumAndMaximumSupplier.hxx> + +#include <com/sun/star/chart/TimeUnit.hpp> + +#include <cmath> +#include <limits> + +namespace chart +{ +using namespace ::com::sun::star; + +MergedMinimumAndMaximumSupplier::MergedMinimumAndMaximumSupplier() +{ +} + +MergedMinimumAndMaximumSupplier::~MergedMinimumAndMaximumSupplier() +{ +} + +void MergedMinimumAndMaximumSupplier::addMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier ) +{ + m_aMinimumAndMaximumSupplierList.insert( pMinimumAndMaximumSupplier ); +} + +bool MergedMinimumAndMaximumSupplier::hasMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier ) +{ + return m_aMinimumAndMaximumSupplierList.count( pMinimumAndMaximumSupplier ) != 0; +} + +double MergedMinimumAndMaximumSupplier::getMinimumX() +{ + double fGlobalExtremum = std::numeric_limits<double>::infinity(); + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + double fLocalExtremum = elem->getMinimumX(); + if(fLocalExtremum<fGlobalExtremum) + fGlobalExtremum=fLocalExtremum; + } + if(std::isinf(fGlobalExtremum)) + return std::numeric_limits<double>::quiet_NaN(); + return fGlobalExtremum; +} + +double MergedMinimumAndMaximumSupplier::getMaximumX() +{ + double fGlobalExtremum = -std::numeric_limits<double>::infinity(); + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + double fLocalExtremum = elem->getMaximumX(); + if(fLocalExtremum>fGlobalExtremum) + fGlobalExtremum=fLocalExtremum; + } + if(std::isinf(fGlobalExtremum)) + return std::numeric_limits<double>::quiet_NaN(); + return fGlobalExtremum; +} + +double MergedMinimumAndMaximumSupplier::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) +{ + double fGlobalExtremum = std::numeric_limits<double>::infinity(); + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + double fLocalExtremum = elem->getMinimumYInRange( fMinimumX, fMaximumX, nAxisIndex ); + if(fLocalExtremum<fGlobalExtremum) + fGlobalExtremum=fLocalExtremum; + } + if(std::isinf(fGlobalExtremum)) + return std::numeric_limits<double>::quiet_NaN(); + return fGlobalExtremum; +} + +double MergedMinimumAndMaximumSupplier::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) +{ + double fGlobalExtremum = -std::numeric_limits<double>::infinity(); + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + double fLocalExtremum = elem->getMaximumYInRange( fMinimumX, fMaximumX, nAxisIndex ); + if(fLocalExtremum>fGlobalExtremum) + fGlobalExtremum=fLocalExtremum; + } + if(std::isinf(fGlobalExtremum)) + return std::numeric_limits<double>::quiet_NaN(); + return fGlobalExtremum; +} + +double MergedMinimumAndMaximumSupplier::getMinimumZ() +{ + double fGlobalExtremum = std::numeric_limits<double>::infinity(); + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + double fLocalExtremum = elem->getMinimumZ(); + if(fLocalExtremum<fGlobalExtremum) + fGlobalExtremum=fLocalExtremum; + } + if(std::isinf(fGlobalExtremum)) + return std::numeric_limits<double>::quiet_NaN(); + return fGlobalExtremum; +} + +double MergedMinimumAndMaximumSupplier::getMaximumZ() +{ + double fGlobalExtremum = -std::numeric_limits<double>::infinity(); + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + double fLocalExtremum = elem->getMaximumZ(); + if(fLocalExtremum>fGlobalExtremum) + fGlobalExtremum=fLocalExtremum; + } + if(std::isinf(fGlobalExtremum)) + return std::numeric_limits<double>::quiet_NaN(); + return fGlobalExtremum; +} + +bool MergedMinimumAndMaximumSupplier::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex ) +{ + // only return true, if *all* suppliers want to scale to the main tick marks + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + if( !elem->isExpandBorderToIncrementRhythm( nDimensionIndex ) ) + return false; + return true; +} + +bool MergedMinimumAndMaximumSupplier::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) +{ + // only return true, if *all* suppliers want to expand the range + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + if( !elem->isExpandIfValuesCloseToBorder( nDimensionIndex ) ) + return false; + return true; +} + +bool MergedMinimumAndMaximumSupplier::isExpandWideValuesToZero( sal_Int32 nDimensionIndex ) +{ + // already return true, if at least one supplier wants to expand the range + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + if( elem->isExpandWideValuesToZero( nDimensionIndex ) ) + return true; + return false; +} + +bool MergedMinimumAndMaximumSupplier::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) +{ + // already return true, if at least one supplier wants to expand the range + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + if( elem->isExpandNarrowValuesTowardZero( nDimensionIndex ) ) + return true; + return false; +} + +bool MergedMinimumAndMaximumSupplier::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) +{ + // should not be called + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + if( elem->isSeparateStackingForDifferentSigns( nDimensionIndex ) ) + return true; + return false; +} + +void MergedMinimumAndMaximumSupplier::clearMinimumAndMaximumSupplierList() +{ + m_aMinimumAndMaximumSupplierList.clear(); +} + +tools::Long MergedMinimumAndMaximumSupplier::calculateTimeResolutionOnXAxis() +{ + tools::Long nRet = css::chart::TimeUnit::YEAR; + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + { + tools::Long nCurrent = elem->calculateTimeResolutionOnXAxis(); + if(nRet>nCurrent) + nRet=nCurrent; + } + return nRet; +} + +void MergedMinimumAndMaximumSupplier::setTimeResolutionOnXAxis( tools::Long nTimeResolution, const Date& rNullDate ) +{ + for (auto const& elem : m_aMinimumAndMaximumSupplierList) + elem->setTimeResolutionOnXAxis( nTimeResolution, rNullDate ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/ScaleAutomatism.cxx b/chart2/source/view/axes/ScaleAutomatism.cxx new file mode 100644 index 000000000..24195c8fd --- /dev/null +++ b/chart2/source/view/axes/ScaleAutomatism.cxx @@ -0,0 +1,989 @@ +/* -*- 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 <ScaleAutomatism.hxx> +#include "Tickmarks_Equidistant.hxx" +#include <DateHelper.hxx> +#include "DateScaling.hxx" +#include <AxisHelper.hxx> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart2/AxisType.hpp> + +#include <rtl/math.hxx> +#include <tools/long.hxx> +#include <limits> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::chart::TimeUnit::DAY; +using ::com::sun::star::chart::TimeUnit::MONTH; +using ::com::sun::star::chart::TimeUnit::YEAR; + +const sal_Int32 MAXIMUM_MANUAL_INCREMENT_COUNT = 500; +const sal_Int32 MAXIMUM_SUB_INCREMENT_COUNT = 100; + +static sal_Int32 lcl_getMaximumAutoIncrementCount( sal_Int32 nAxisType ) +{ + sal_Int32 nMaximumAutoIncrementCount = 10; + if( nAxisType==AxisType::DATE ) + nMaximumAutoIncrementCount = MAXIMUM_MANUAL_INCREMENT_COUNT; + return nMaximumAutoIncrementCount; +} + +namespace +{ + +void lcl_ensureMaximumSubIncrementCount( sal_Int32& rnSubIntervalCount ) +{ + if( rnSubIntervalCount > MAXIMUM_SUB_INCREMENT_COUNT ) + rnSubIntervalCount = MAXIMUM_SUB_INCREMENT_COUNT; +} + +}//end anonymous namespace + +ExplicitScaleData::ExplicitScaleData() + : Minimum(0.0) + , Maximum(10.0) + , Origin(0.0) + , Orientation(css::chart2::AxisOrientation_MATHEMATICAL) + , AxisType(css::chart2::AxisType::REALNUMBER) + , m_bShiftedCategoryPosition(false) + , TimeResolution(css::chart::TimeUnit::DAY) + , NullDate(30,12,1899) +{ +} + +ExplicitSubIncrement::ExplicitSubIncrement() + : IntervalCount(2) + , PostEquidistant(true) +{ +} + +ExplicitIncrementData::ExplicitIncrementData() + : MajorTimeInterval(1,css::chart::TimeUnit::DAY) + , MinorTimeInterval(1,css::chart::TimeUnit::DAY) + , Distance(1.0) + , PostEquidistant(true) + , BaseValue(0.0) +{ +} + +ScaleAutomatism::ScaleAutomatism( const ScaleData& rSourceScale, const Date& rNullDate ) + : m_aSourceScale( rSourceScale ) + , m_fValueMinimum( 0.0 ) + , m_fValueMaximum( 0.0 ) + , m_nMaximumAutoMainIncrementCount( lcl_getMaximumAutoIncrementCount( rSourceScale.AxisType ) ) + , m_bExpandBorderToIncrementRhythm( false ) + , m_bExpandIfValuesCloseToBorder( false ) + , m_bExpandWideValuesToZero( false ) + , m_bExpandNarrowValuesTowardZero( false ) + , m_nTimeResolution(css::chart::TimeUnit::DAY) + , m_aNullDate(rNullDate) +{ + resetValueRange(); + + double fExplicitOrigin = 0.0; + if( m_aSourceScale.Origin >>= fExplicitOrigin ) + expandValueRange( fExplicitOrigin, fExplicitOrigin); +} + +void ScaleAutomatism::resetValueRange( ) +{ + m_fValueMinimum = std::numeric_limits<double>::quiet_NaN(); + m_fValueMaximum = std::numeric_limits<double>::quiet_NaN(); +} + +void ScaleAutomatism::expandValueRange( double fMinimum, double fMaximum ) +{ + // if m_fValueMinimum and m_fValueMaximum == 0, it means that they were not determined. + // m_fValueMinimum == 0 makes impossible to determine real minimum, + // so they need to be reset tdf#96807 + if( (m_fValueMinimum == 0.0) && (m_fValueMaximum == 0.0) ) + resetValueRange(); + if( (fMinimum < m_fValueMinimum) || std::isnan( m_fValueMinimum ) ) + m_fValueMinimum = fMinimum; + if( (fMaximum > m_fValueMaximum) || std::isnan( m_fValueMaximum ) ) + m_fValueMaximum = fMaximum; +} + +void ScaleAutomatism::setAutoScalingOptions( + bool bExpandBorderToIncrementRhythm, + bool bExpandIfValuesCloseToBorder, + bool bExpandWideValuesToZero, + bool bExpandNarrowValuesTowardZero ) +{ + // if called multiple times, enable an option, if it is set in at least one call + m_bExpandBorderToIncrementRhythm |= bExpandBorderToIncrementRhythm; + m_bExpandIfValuesCloseToBorder |= bExpandIfValuesCloseToBorder; + m_bExpandWideValuesToZero |= bExpandWideValuesToZero; + m_bExpandNarrowValuesTowardZero |= bExpandNarrowValuesTowardZero; + + if( m_aSourceScale.AxisType==AxisType::PERCENT ) + m_bExpandIfValuesCloseToBorder = false; +} + +void ScaleAutomatism::setMaximumAutoMainIncrementCount( sal_Int32 nMaximumAutoMainIncrementCount ) +{ + if( nMaximumAutoMainIncrementCount < 2 ) + m_nMaximumAutoMainIncrementCount = 2; //#i82006 + else if( nMaximumAutoMainIncrementCount > lcl_getMaximumAutoIncrementCount( m_aSourceScale.AxisType ) ) + m_nMaximumAutoMainIncrementCount = lcl_getMaximumAutoIncrementCount( m_aSourceScale.AxisType ); + else + m_nMaximumAutoMainIncrementCount = nMaximumAutoMainIncrementCount; +} + +void ScaleAutomatism::setAutomaticTimeResolution( sal_Int32 nTimeResolution ) +{ + m_nTimeResolution = nTimeResolution; +} + +void ScaleAutomatism::calculateExplicitScaleAndIncrement( + ExplicitScaleData& rExplicitScale, ExplicitIncrementData& rExplicitIncrement ) const +{ + // fill explicit scale + rExplicitScale.Orientation = m_aSourceScale.Orientation; + rExplicitScale.Scaling = m_aSourceScale.Scaling; + rExplicitScale.AxisType = m_aSourceScale.AxisType; + rExplicitScale.NullDate = m_aNullDate; + + bool bAutoMinimum = !(m_aSourceScale.Minimum >>= rExplicitScale.Minimum); + bool bAutoMaximum = !(m_aSourceScale.Maximum >>= rExplicitScale.Maximum); + bool bAutoOrigin = !(m_aSourceScale.Origin >>= rExplicitScale.Origin); + + // automatic scale minimum + if( bAutoMinimum ) + { + if( m_aSourceScale.AxisType==AxisType::PERCENT ) + rExplicitScale.Minimum = 0.0; + else if( std::isnan( m_fValueMinimum ) ) + { + if( m_aSourceScale.AxisType==AxisType::DATE ) + rExplicitScale.Minimum = 36526.0; //1.1.2000 + else + rExplicitScale.Minimum = 0.0; //@todo get Minimum from scaling or from plotter???? + } + else + rExplicitScale.Minimum = m_fValueMinimum; + } + + // automatic scale maximum + if( bAutoMaximum ) + { + if( m_aSourceScale.AxisType==AxisType::PERCENT ) + rExplicitScale.Maximum = 1.0; + else if( std::isnan( m_fValueMaximum ) ) + { + if( m_aSourceScale.AxisType==AxisType::DATE ) + rExplicitScale.Maximum = 40179.0; //1.1.2010 + else + rExplicitScale.Maximum = 10.0; //@todo get Maximum from scaling or from plotter???? + } + else + rExplicitScale.Maximum = m_fValueMaximum; + } + + //fill explicit increment + + rExplicitScale.m_bShiftedCategoryPosition = m_aSourceScale.ShiftedCategoryPosition; + bool bIsLogarithm = false; + + //minimum and maximum of the ExplicitScaleData may be changed if allowed + if( m_aSourceScale.AxisType==AxisType::DATE ) + calculateExplicitIncrementAndScaleForDateTimeAxis( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + else if( m_aSourceScale.AxisType==AxisType::CATEGORY || m_aSourceScale.AxisType==AxisType::SERIES ) + calculateExplicitIncrementAndScaleForCategory( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + else + { + bIsLogarithm = AxisHelper::isLogarithmic( rExplicitScale.Scaling ); + if( bIsLogarithm ) + calculateExplicitIncrementAndScaleForLogarithmic( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + else + calculateExplicitIncrementAndScaleForLinear( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + } + + // automatic origin + if( bAutoOrigin ) + { + // #i71415# automatic origin for logarithmic axis + double fDefaulOrigin = bIsLogarithm ? 1.0 : 0.0; + + if( fDefaulOrigin < rExplicitScale.Minimum ) + fDefaulOrigin = rExplicitScale.Minimum; + else if( fDefaulOrigin > rExplicitScale.Maximum ) + fDefaulOrigin = rExplicitScale.Maximum; + + rExplicitScale.Origin = fDefaulOrigin; + } +} + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForCategory( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // no scaling for categories + rExplicitScale.Scaling.clear(); + + if( rExplicitScale.m_bShiftedCategoryPosition ) + rExplicitScale.Maximum += 1.0; + + // ensure that at least one category is visible + if( rExplicitScale.Maximum <= rExplicitScale.Minimum ) + rExplicitScale.Maximum = rExplicitScale.Minimum + 1.0; + + // default increment settings + rExplicitIncrement.PostEquidistant = true; // does not matter anyhow + rExplicitIncrement.Distance = 1.0; // category axis always have a main increment of 1 + rExplicitIncrement.BaseValue = 0.0; // category axis always have a base of 0 + + // automatic minimum and maximum + if( bAutoMinimum && m_bExpandBorderToIncrementRhythm ) + rExplicitScale.Minimum = EquidistantTickFactory::getMinimumAtIncrement( rExplicitScale.Minimum, rExplicitIncrement ); + if( bAutoMaximum && m_bExpandBorderToIncrementRhythm ) + rExplicitScale.Maximum = EquidistantTickFactory::getMaximumAtIncrement( rExplicitScale.Maximum, rExplicitIncrement ); + + //prevent performance killover + double fDistanceCount = ::rtl::math::approxFloor( (rExplicitScale.Maximum-rExplicitScale.Minimum) / rExplicitIncrement.Distance ); + if( static_cast< sal_Int32 >( fDistanceCount ) > MAXIMUM_MANUAL_INCREMENT_COUNT ) + { + double fMinimumFloor = ::rtl::math::approxFloor( rExplicitScale.Minimum ); + double fMaximumCeil = ::rtl::math::approxCeil( rExplicitScale.Maximum ); + rExplicitIncrement.Distance = ::rtl::math::approxCeil( (fMaximumCeil - fMinimumFloor) / MAXIMUM_MANUAL_INCREMENT_COUNT ); + } + + //fill explicit sub increment + sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength(); + for( sal_Int32 nN=0; nN<nSubCount; nN++ ) + { + ExplicitSubIncrement aExplicitSubIncrement; + const SubIncrement& rSubIncrement= m_aSourceScale.IncrementData.SubIncrements[nN]; + if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount)) + { + //scaling dependent + //@todo autocalculate IntervalCount dependent on MainIncrement and scaling + aExplicitSubIncrement.IntervalCount = 2; + } + lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount ); + if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant)) + { + //scaling dependent + aExplicitSubIncrement.PostEquidistant = false; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForLogarithmic( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // *** STEP 1: initialize the range data *** + + const double fInputMinimum = rExplicitScale.Minimum; + const double fInputMaximum = rExplicitScale.Maximum; + + double fSourceMinimum = rExplicitScale.Minimum; + double fSourceMaximum = rExplicitScale.Maximum; + + // set automatic PostEquidistant to true (maybe scaling dependent?) + // Note: scaling with PostEquidistant==false is untested and needs review + if( !(m_aSourceScale.IncrementData.PostEquidistant >>= rExplicitIncrement.PostEquidistant) ) + rExplicitIncrement.PostEquidistant = true; + + /* All following scaling code will operate on the logarithms of the source + values. In the last step, the original values will be restored. */ + uno::Reference< XScaling > xScaling = rExplicitScale.Scaling; + if( !xScaling.is() ) + xScaling.set( AxisHelper::createLogarithmicScaling() ); + uno::Reference< XScaling > xInverseScaling = xScaling->getInverseScaling(); + + fSourceMinimum = xScaling->doScaling( fSourceMinimum ); + if( !std::isfinite( fSourceMinimum ) ) + fSourceMinimum = 0.0; + else if( ::rtl::math::approxEqual( fSourceMinimum, ::rtl::math::approxFloor( fSourceMinimum ) ) ) + fSourceMinimum = ::rtl::math::approxFloor( fSourceMinimum ); + + fSourceMaximum = xScaling->doScaling( fSourceMaximum ); + if( !std::isfinite( fSourceMaximum ) ) + fSourceMaximum = 0.0; + else if( ::rtl::math::approxEqual( fSourceMaximum, ::rtl::math::approxFloor( fSourceMaximum ) ) ) + fSourceMaximum = ::rtl::math::approxFloor( fSourceMaximum ); + + /* If range is invalid (minimum greater than maximum), change one of the + variable limits to validate the range. In this step, a zero-sized range + is still allowed. */ + if( fSourceMinimum > fSourceMaximum ) + { + // force changing the maximum, if both limits are fixed + if( bAutoMaximum || !bAutoMinimum ) + fSourceMaximum = fSourceMinimum; + else + fSourceMinimum = fSourceMaximum; + } + + /* If maximum is less than 0 (and therefore minimum too), minimum and + maximum will be negated and swapped to make the following algorithms + easier. Example: Both ranges [2,5] and [-5,-2] will be processed as + [2,5], and the latter will be swapped back later. The range [0,0] is + explicitly excluded from swapping (this would result in [-1,0] instead + of the expected [0,1]). */ + bool bSwapAndNegateRange = (fSourceMinimum < 0.0) && (fSourceMaximum <= 0.0); + if( bSwapAndNegateRange ) + { + double fTempValue = fSourceMinimum; + fSourceMinimum = -fSourceMaximum; + fSourceMaximum = -fTempValue; + std::swap( bAutoMinimum, bAutoMaximum ); + } + + // *** STEP 2: find temporary (unrounded) axis minimum and maximum *** + + double fTempMinimum = fSourceMinimum; + double fTempMaximum = fSourceMaximum; + + /* If minimum is variable and greater than 0 (and therefore maximum too), + means all original values are greater than 1 (or all values are less + than 1, and the range has been swapped above), then: */ + if( bAutoMinimum && (fTempMinimum > 0.0) ) + { + double fMinimumFloor = ::rtl::math::approxFloor( fTempMinimum ); + double fMaximumFloor = ::rtl::math::approxFloor( fTempMaximum ); + // handle the exact value B^(n+1) to be in the range [B^n,B^(n+1)] + if( ::rtl::math::approxEqual( fTempMaximum, fMaximumFloor ) ) + fMaximumFloor -= 1.0; + + if( fMinimumFloor == fMaximumFloor ) + { + /* if minimum and maximum are in one increment interval, expand + minimum toward 0 to make the 'shorter' data points visible. */ + if( m_bExpandNarrowValuesTowardZero ) + fTempMinimum -= 1.0; + } + } + + /* If range is still zero-sized (e.g. when minimum is fixed), set minimum + to 0, which makes the axis start/stop at the value 1. */ + if( fTempMinimum == fTempMaximum ) + { + if( bAutoMinimum && (fTempMaximum > 0.0) ) + fTempMinimum = 0.0; + else + fTempMaximum += 1.0; // always add one interval, even if maximum is fixed + } + + // *** STEP 3: calculate main interval size *** + + // base value (anchor position of the intervals), already scaled + if( !(m_aSourceScale.IncrementData.BaseValue >>= rExplicitIncrement.BaseValue) ) + { + //scaling dependent + //@maybe todo is this default also plotter dependent ?? + if( !bAutoMinimum ) + rExplicitIncrement.BaseValue = fTempMinimum; + else if( !bAutoMaximum ) + rExplicitIncrement.BaseValue = fTempMaximum; + else + rExplicitIncrement.BaseValue = 0.0; + } + + // calculate automatic interval + bool bAutoDistance = !(m_aSourceScale.IncrementData.Distance >>= rExplicitIncrement.Distance); + if( bAutoDistance ) + rExplicitIncrement.Distance = 0.0; + + /* Restrict number of allowed intervals with user-defined distance to + MAXIMUM_MANUAL_INCREMENT_COUNT. */ + sal_Int32 nMaxMainIncrementCount = bAutoDistance ? + m_nMaximumAutoMainIncrementCount : MAXIMUM_MANUAL_INCREMENT_COUNT; + + // repeat calculation until number of intervals are valid + bool bNeedIteration = true; + bool bHasCalculatedDistance = false; + while( bNeedIteration ) + { + if( bAutoDistance ) + { + // first iteration: calculate interval size from axis limits + if( !bHasCalculatedDistance ) + { + double fMinimumFloor = ::rtl::math::approxFloor( fTempMinimum ); + double fMaximumCeil = ::rtl::math::approxCeil( fTempMaximum ); + rExplicitIncrement.Distance = ::rtl::math::approxCeil( (fMaximumCeil - fMinimumFloor) / nMaxMainIncrementCount ); + } + else + { + // following iterations: increase distance + rExplicitIncrement.Distance += 1.0; + } + + // for next iteration: distance calculated -> use else path to increase + bHasCalculatedDistance = true; + } + + // *** STEP 4: additional space above or below the data points *** + + double fAxisMinimum = fTempMinimum; + double fAxisMaximum = fTempMaximum; + + // round to entire multiples of the distance and add additional space + if( bAutoMinimum && m_bExpandBorderToIncrementRhythm ) + { + fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement ); + + //ensure valid values after scaling #i100995# + if( !bAutoDistance ) + { + double fCheck = xInverseScaling->doScaling( fAxisMinimum ); + if( !std::isfinite( fCheck ) || fCheck <= 0 ) + { + bAutoDistance = true; + bHasCalculatedDistance = false; + continue; + } + } + } + if( bAutoMaximum && m_bExpandBorderToIncrementRhythm ) + { + fAxisMaximum = EquidistantTickFactory::getMaximumAtIncrement( fAxisMaximum, rExplicitIncrement ); + + //ensure valid values after scaling #i100995# + if( !bAutoDistance ) + { + double fCheck = xInverseScaling->doScaling( fAxisMaximum ); + if( !std::isfinite( fCheck ) || fCheck <= 0 ) + { + bAutoDistance = true; + bHasCalculatedDistance = false; + continue; + } + } + } + + // set the resulting limits (swap back to negative range if needed) + if( bSwapAndNegateRange ) + { + rExplicitScale.Minimum = -fAxisMaximum; + rExplicitScale.Maximum = -fAxisMinimum; + } + else + { + rExplicitScale.Minimum = fAxisMinimum; + rExplicitScale.Maximum = fAxisMaximum; + } + + /* If the number of intervals is too high (e.g. due to invalid fixed + distance or due to added space above or below data points), + calculate again with increased distance. */ + double fDistanceCount = ::rtl::math::approxFloor( (fAxisMaximum - fAxisMinimum) / rExplicitIncrement.Distance ); + bNeedIteration = static_cast< sal_Int32 >( fDistanceCount ) > nMaxMainIncrementCount; + // if manual distance is invalid, trigger automatic calculation + if( bNeedIteration ) + bAutoDistance = true; + + // convert limits back to logarithmic scale + rExplicitScale.Minimum = xInverseScaling->doScaling( rExplicitScale.Minimum ); + rExplicitScale.Maximum = xInverseScaling->doScaling( rExplicitScale.Maximum ); + + //ensure valid values after scaling #i100995# + if( !std::isfinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0) + { + rExplicitScale.Minimum = fInputMinimum; + if( !std::isfinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0 ) + rExplicitScale.Minimum = 1.0; + } + if( !std::isfinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 ) + { + rExplicitScale.Maximum= fInputMaximum; + if( !std::isfinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 ) + rExplicitScale.Maximum = 10.0; + } + if( rExplicitScale.Maximum < rExplicitScale.Minimum ) + std::swap( rExplicitScale.Maximum, rExplicitScale.Minimum ); + } + + //fill explicit sub increment + sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength(); + for( sal_Int32 nN=0; nN<nSubCount; nN++ ) + { + ExplicitSubIncrement aExplicitSubIncrement; + const SubIncrement& rSubIncrement = m_aSourceScale.IncrementData.SubIncrements[nN]; + if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount)) + { + //scaling dependent + //@todo autocalculate IntervalCount dependent on MainIncrement and scaling + aExplicitSubIncrement.IntervalCount = 9; + } + lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount ); + if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant)) + { + //scaling dependent + aExplicitSubIncrement.PostEquidistant = false; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForDateTimeAxis( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + Date aMinDate(m_aNullDate); aMinDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Minimum)); + Date aMaxDate(m_aNullDate); aMaxDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Maximum)); + rExplicitIncrement.PostEquidistant = false; + + if( aMinDate > aMaxDate ) + { + std::swap(aMinDate,aMaxDate); + } + + if( !(m_aSourceScale.TimeIncrement.TimeResolution >>= rExplicitScale.TimeResolution) ) + rExplicitScale.TimeResolution = m_nTimeResolution; + + rExplicitScale.Scaling = new DateScaling(m_aNullDate,rExplicitScale.TimeResolution,false); + + // choose min and max suitable to time resolution + switch( rExplicitScale.TimeResolution ) + { + case DAY: + if( rExplicitScale.m_bShiftedCategoryPosition ) + ++aMaxDate; //for explicit scales we need one interval more (maximum excluded) + break; + case MONTH: + aMinDate.SetDay(1); + aMaxDate.SetDay(1); + if( rExplicitScale.m_bShiftedCategoryPosition ) + aMaxDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,1);//for explicit scales we need one interval more (maximum excluded) + if( DateHelper::IsLessThanOneMonthAway( aMinDate, aMaxDate ) ) + { + if( bAutoMaximum || !bAutoMinimum ) + aMaxDate = DateHelper::GetDateSomeMonthsAway(aMinDate,1); + else + aMinDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,-1); + } + break; + case YEAR: + aMinDate.SetDay(1); + aMinDate.SetMonth(1); + aMaxDate.SetDay(1); + aMaxDate.SetMonth(1); + if( rExplicitScale.m_bShiftedCategoryPosition ) + aMaxDate = DateHelper::GetDateSomeYearsAway(aMaxDate,1);//for explicit scales we need one interval more (maximum excluded) + if( DateHelper::IsLessThanOneYearAway( aMinDate, aMaxDate ) ) + { + if( bAutoMaximum || !bAutoMinimum ) + aMaxDate = DateHelper::GetDateSomeYearsAway(aMinDate,1); + else + aMinDate = DateHelper::GetDateSomeYearsAway(aMaxDate,-1); + } + break; + } + + // set the resulting limits (swap back to negative range if needed) + rExplicitScale.Minimum = aMinDate - m_aNullDate; + rExplicitScale.Maximum = aMaxDate - m_aNullDate; + + bool bAutoMajor = !(m_aSourceScale.TimeIncrement.MajorTimeInterval >>= rExplicitIncrement.MajorTimeInterval); + bool bAutoMinor = !(m_aSourceScale.TimeIncrement.MinorTimeInterval >>= rExplicitIncrement.MinorTimeInterval); + + sal_Int32 nMaxMainIncrementCount = bAutoMajor ? + m_nMaximumAutoMainIncrementCount : MAXIMUM_MANUAL_INCREMENT_COUNT; + if( nMaxMainIncrementCount > 1 ) + nMaxMainIncrementCount--; + + //choose major time interval: + tools::Long nDayCount = aMaxDate - aMinDate; + tools::Long nMainIncrementCount = 1; + if( !bAutoMajor ) + { + tools::Long nIntervalDayCount = rExplicitIncrement.MajorTimeInterval.Number; + if( rExplicitIncrement.MajorTimeInterval.TimeUnit < rExplicitScale.TimeResolution ) + rExplicitIncrement.MajorTimeInterval.TimeUnit = rExplicitScale.TimeResolution; + switch( rExplicitIncrement.MajorTimeInterval.TimeUnit ) + { + case DAY: + break; + case MONTH: + nIntervalDayCount*=31;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + case YEAR: + nIntervalDayCount*=365;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + } + nMainIncrementCount = nDayCount/nIntervalDayCount; + if( nMainIncrementCount > nMaxMainIncrementCount ) + bAutoMajor = true; + } + if( bAutoMajor ) + { + tools::Long nNumer = 1; + tools::Long nIntervalDays = nDayCount / nMaxMainIncrementCount; + double nDaysPerInterval = 1.0; + if( nIntervalDays>365 || rExplicitScale.TimeResolution==YEAR ) + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = YEAR; + nDaysPerInterval = 365.0;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + } + else if( nIntervalDays>31 || rExplicitScale.TimeResolution==MONTH ) + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = MONTH; + nDaysPerInterval = 31.0;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + } + else + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = DAY; + nDaysPerInterval = 1.0; + } + + nNumer = static_cast<sal_Int32>( rtl::math::approxFloor( nIntervalDays/nDaysPerInterval ) ); + if(nNumer<=0) + nNumer=1; + if( rExplicitIncrement.MajorTimeInterval.TimeUnit == DAY ) + { + if( nNumer>2 && nNumer<7 ) + nNumer=7; + else if( nNumer>7 ) + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = MONTH; + nDaysPerInterval = 31.0; + nNumer = static_cast<sal_Int32>( rtl::math::approxFloor( nIntervalDays/nDaysPerInterval ) ); + if(nNumer<=0) + nNumer=1; + } + } + rExplicitIncrement.MajorTimeInterval.Number = nNumer; + nMainIncrementCount = static_cast<tools::Long>(nDayCount/(nNumer*nDaysPerInterval)); + } + + //choose minor time interval: + if( !bAutoMinor ) + { + if( rExplicitIncrement.MinorTimeInterval.TimeUnit > rExplicitIncrement.MajorTimeInterval.TimeUnit ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit; + tools::Long nIntervalDayCount = rExplicitIncrement.MinorTimeInterval.Number; + switch( rExplicitIncrement.MinorTimeInterval.TimeUnit ) + { + case DAY: + break; + case MONTH: + nIntervalDayCount*=31;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + case YEAR: + nIntervalDayCount*=365;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + } + if( nDayCount/nIntervalDayCount > nMaxMainIncrementCount ) + bAutoMinor = true; + } + if( !bAutoMinor ) + return; + + rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit; + rExplicitIncrement.MinorTimeInterval.Number = 1; + if( nMainIncrementCount > 100 ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number; + else + { + if( rExplicitIncrement.MajorTimeInterval.Number >= 2 ) + { + if( !(rExplicitIncrement.MajorTimeInterval.Number%2) ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/2; + else if( !(rExplicitIncrement.MajorTimeInterval.Number%3) ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/3; + else if( !(rExplicitIncrement.MajorTimeInterval.Number%5) ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/5; + else if( rExplicitIncrement.MajorTimeInterval.Number > 50 ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number; + } + else + { + switch( rExplicitIncrement.MajorTimeInterval.TimeUnit ) + { + case DAY: + break; + case MONTH: + if( rExplicitScale.TimeResolution == DAY ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = DAY; + break; + case YEAR: + if( rExplicitScale.TimeResolution <= MONTH ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = MONTH; + break; + } + } + } + +} + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForLinear( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // *** STEP 1: initialize the range data *** + + double fSourceMinimum = rExplicitScale.Minimum; + double fSourceMaximum = rExplicitScale.Maximum; + + // set automatic PostEquidistant to true (maybe scaling dependent?) + if( !(m_aSourceScale.IncrementData.PostEquidistant >>= rExplicitIncrement.PostEquidistant) ) + rExplicitIncrement.PostEquidistant = true; + + /* If range is invalid (minimum greater than maximum), change one of the + variable limits to validate the range. In this step, a zero-sized range + is still allowed. */ + if( fSourceMinimum > fSourceMaximum ) + { + // force changing the maximum, if both limits are fixed + if( bAutoMaximum || !bAutoMinimum ) + fSourceMaximum = fSourceMinimum; + else + fSourceMinimum = fSourceMaximum; + } + + /* If maximum is zero or negative (and therefore minimum too), minimum and + maximum will be negated and swapped to make the following algorithms + easier. Example: Both ranges [2,5] and [-5,-2] will be processed as + [2,5], and the latter will be swapped back later. The range [0,0] is + explicitly excluded from swapping (this would result in [-1,0] instead + of the expected [0,1]). */ + bool bSwapAndNegateRange = (fSourceMinimum < 0.0) && (fSourceMaximum <= 0.0); + if( bSwapAndNegateRange ) + { + double fTempValue = fSourceMinimum; + fSourceMinimum = -fSourceMaximum; + fSourceMaximum = -fTempValue; + std::swap( bAutoMinimum, bAutoMaximum ); + } + + // *** STEP 2: find temporary (unrounded) axis minimum and maximum *** + + double fTempMinimum = fSourceMinimum; + double fTempMaximum = fSourceMaximum; + + /* If minimum is variable and greater than 0 (and therefore maximum too), + means all values are positive (or all values are negative, and the + range has been swapped above), then: */ + if( bAutoMinimum && (fTempMinimum > 0.0) ) + { + /* If minimum equals maximum, or if minimum is less than 5/6 of + maximum, set minimum to 0. */ + if( (fTempMinimum == fTempMaximum) || (fTempMinimum / fTempMaximum < 5.0 / 6.0) ) + { + if( m_bExpandWideValuesToZero ) + fTempMinimum = 0.0; + } + /* Else (minimum is greater than or equal to 5/6 of maximum), add half + of the visible range (expand minimum toward 0) to make the + 'shorter' data points visible. */ + else + { + if( m_bExpandNarrowValuesTowardZero ) + fTempMinimum -= (fTempMaximum - fTempMinimum) / 2.0; + } + } + + /* If range is still zero-sized (e.g. when minimum is fixed), add some + space to a variable limit. */ + if( fTempMinimum == fTempMaximum ) + { + if( bAutoMaximum || !bAutoMinimum ) + { + // change 0 to 1, otherwise double the value + if( fTempMaximum == 0.0 ) + fTempMaximum = 1.0; + else + fTempMaximum *= 2.0; + } + else + { + // change 0 to -1, otherwise halve the value + if( fTempMinimum == 0.0 ) + fTempMinimum = -1.0; + else + fTempMinimum /= 2.0; + } + } + + // *** STEP 3: calculate main interval size *** + + // base value (anchor position of the intervals) + if( !(m_aSourceScale.IncrementData.BaseValue >>= rExplicitIncrement.BaseValue) ) + { + if( !bAutoMinimum ) + rExplicitIncrement.BaseValue = fTempMinimum; + else if( !bAutoMaximum ) + rExplicitIncrement.BaseValue = fTempMaximum; + else + rExplicitIncrement.BaseValue = 0.0; + } + + // calculate automatic interval + bool bAutoDistance = !(m_aSourceScale.IncrementData.Distance >>= rExplicitIncrement.Distance); + /* Restrict number of allowed intervals with user-defined distance to + MAXIMUM_MANUAL_INCREMENT_COUNT. */ + sal_Int32 nMaxMainIncrementCount = bAutoDistance ? + m_nMaximumAutoMainIncrementCount : MAXIMUM_MANUAL_INCREMENT_COUNT; + + double fDistanceMagnitude = 0.0; + double fDistanceNormalized = 0.0; + bool bHasNormalizedDistance = false; + + // repeat calculation until number of intervals are valid + bool bNeedIteration = true; + while( bNeedIteration ) + { + if( bAutoDistance ) + { + // first iteration: calculate interval size from axis limits + if( !bHasNormalizedDistance ) + { + // raw size of an interval + double fDistance = (fTempMaximum - fTempMinimum) / nMaxMainIncrementCount; + + // if distance of is less than 1e-307, do not do anything + if( fDistance <= 1.0e-307 ) + { + fDistanceNormalized = 1.0; + fDistanceMagnitude = 1.0e-307; + } + else if ( !std::isfinite(fDistance) ) + { + // fdo#43703: Handle values bigger than limits correctly + fDistanceNormalized = 1.0; + fDistanceMagnitude = std::numeric_limits<double>::max(); + } + else + { + // distance magnitude (a power of 10) + int nExponent = static_cast< int >( ::rtl::math::approxFloor( log10( fDistance ) ) ); + fDistanceMagnitude = ::rtl::math::pow10Exp( 1.0, nExponent ); + + // stick normalized distance to a few predefined values + fDistanceNormalized = fDistance / fDistanceMagnitude; + if( fDistanceNormalized <= 1.0 ) + fDistanceNormalized = 1.0; + else if( fDistanceNormalized <= 2.0 ) + fDistanceNormalized = 2.0; + else if( fDistanceNormalized <= 5.0 ) + fDistanceNormalized = 5.0; + else + { + fDistanceNormalized = 1.0; + fDistanceMagnitude *= 10; + } + } + // for next iteration: distance is normalized -> use else path to increase distance + bHasNormalizedDistance = true; + } + // following iterations: increase distance, use only allowed values + else + { + if( fDistanceNormalized == 1.0 ) + fDistanceNormalized = 2.0; + else if( fDistanceNormalized == 2.0 ) + fDistanceNormalized = 5.0; + else + { + fDistanceNormalized = 1.0; + fDistanceMagnitude *= 10; + } + } + + // set the resulting distance + rExplicitIncrement.Distance = fDistanceNormalized * fDistanceMagnitude; + } + + // *** STEP 4: additional space above or below the data points *** + + double fAxisMinimum = fTempMinimum; + double fAxisMaximum = fTempMaximum; + + // round to entire multiples of the distance and add additional space + if( bAutoMinimum ) + { + // round to entire multiples of the distance, based on the base value + if( m_bExpandBorderToIncrementRhythm ) + fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement ); + // additional space, if source minimum is to near at axis minimum + if( m_bExpandIfValuesCloseToBorder ) + if( (fAxisMinimum != 0.0) && ((fAxisMaximum - fSourceMinimum) / (fAxisMaximum - fAxisMinimum) > 20.0 / 21.0) ) + fAxisMinimum -= rExplicitIncrement.Distance; + } + if( bAutoMaximum ) + { + // round to entire multiples of the distance, based on the base value + if( m_bExpandBorderToIncrementRhythm ) + fAxisMaximum = EquidistantTickFactory::getMaximumAtIncrement( fAxisMaximum, rExplicitIncrement ); + // additional space, if source maximum is to near at axis maximum + if( m_bExpandIfValuesCloseToBorder ) + if( (fAxisMaximum != 0.0) && ((fSourceMaximum - fAxisMinimum) / (fAxisMaximum - fAxisMinimum) > 20.0 / 21.0) ) + fAxisMaximum += rExplicitIncrement.Distance; + } + + // set the resulting limits (swap back to negative range if needed) + if( bSwapAndNegateRange ) + { + rExplicitScale.Minimum = -fAxisMaximum; + rExplicitScale.Maximum = -fAxisMinimum; + } + else + { + rExplicitScale.Minimum = fAxisMinimum; + rExplicitScale.Maximum = fAxisMaximum; + } + + /* If the number of intervals is too high (e.g. due to invalid fixed + distance or due to added space above or below data points), + calculate again with increased distance. */ + double fDistanceCount = ::rtl::math::approxFloor( (fAxisMaximum - fAxisMinimum) / rExplicitIncrement.Distance ); + bNeedIteration = static_cast< sal_Int32 >( fDistanceCount ) > nMaxMainIncrementCount; + // if manual distance is invalid, trigger automatic calculation + if( bNeedIteration ) + bAutoDistance = true; + } + + //fill explicit sub increment + sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength(); + for( sal_Int32 nN=0; nN<nSubCount; nN++ ) + { + ExplicitSubIncrement aExplicitSubIncrement; + const SubIncrement& rSubIncrement= m_aSourceScale.IncrementData.SubIncrements[nN]; + if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount)) + { + //scaling dependent + //@todo autocalculate IntervalCount dependent on MainIncrement and scaling + aExplicitSubIncrement.IntervalCount = 2; + } + lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount ); + if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant)) + { + //scaling dependent + aExplicitSubIncrement.PostEquidistant = false; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/TickmarkProperties.hxx b/chart2/source/view/axes/TickmarkProperties.hxx new file mode 100644 index 000000000..cbb0398b6 --- /dev/null +++ b/chart2/source/view/axes/TickmarkProperties.hxx @@ -0,0 +1,37 @@ +/* -*- 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 . + */ + +#pragma once + +#include <VLineProperties.hxx> + +namespace chart +{ +struct TickmarkProperties +{ + sal_Int32 + RelativePos; //Position in screen values relative to the axis where the tickmark line starts + sal_Int32 Length; //Length of the tickmark line in screen values + + VLineProperties aLineProperties; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/Tickmarks.cxx b/chart2/source/view/axes/Tickmarks.cxx new file mode 100644 index 000000000..01e4a7216 --- /dev/null +++ b/chart2/source/view/axes/Tickmarks.cxx @@ -0,0 +1,317 @@ +/* -*- 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 "Tickmarks.hxx" +#include "Tickmarks_Equidistant.hxx" +#include "Tickmarks_Dates.hxx" +#include <ViewDefines.hxx> +#include "VAxisProperties.hxx" +#include <osl/diagnose.h> +#include <com/sun/star/chart2/AxisType.hpp> + +using namespace ::com::sun::star; +using ::basegfx::B2DVector; + +namespace chart { + +TickInfo::TickInfo( const uno::Reference<chart2::XScaling>& xInverse ) +: fScaledTickValue( 0.0 ) +, xInverseScaling( xInverse ) +, aTickScreenPosition(0.0,0.0) +, nFactorForLimitedTextWidth(1) +, bPaintIt( true ) +{ +} + +double TickInfo::getUnscaledTickValue() const +{ + if( xInverseScaling.is() ) + return xInverseScaling->doScaling( fScaledTickValue ); + else + return fScaledTickValue; +} + +sal_Int32 TickInfo::getScreenDistanceBetweenTicks( const TickInfo& rOherTickInfo ) const +{ + //return the positive distance between the two first tickmarks in screen values + + B2DVector aDistance = rOherTickInfo.aTickScreenPosition - aTickScreenPosition; + sal_Int32 nRet = static_cast<sal_Int32>(aDistance.getLength()); + if(nRet<0) + nRet *= -1; + return nRet; +} + +PureTickIter::PureTickIter( TickInfoArrayType& rTickInfoVector ) + : m_rTickVector(rTickInfoVector) + , m_aTickIter(m_rTickVector.begin()) +{ +} +PureTickIter::~PureTickIter() +{ +} +TickInfo* PureTickIter::firstInfo() +{ + m_aTickIter = m_rTickVector.begin(); + if(m_aTickIter!=m_rTickVector.end()) + return &*m_aTickIter; + return nullptr; +} +TickInfo* PureTickIter::nextInfo() +{ + if(m_aTickIter!=m_rTickVector.end()) + { + ++m_aTickIter; + if(m_aTickIter!=m_rTickVector.end()) + return &*m_aTickIter; + } + return nullptr; +} + +TickFactory::TickFactory( + const ExplicitScaleData& rScale, const ExplicitIncrementData& rIncrement ) + : m_rScale( rScale ) + , m_rIncrement( rIncrement ) +{ + //@todo: make sure that the scale is valid for the scaling + + if( m_rScale.Scaling.is() ) + { + m_xInverseScaling = m_rScale.Scaling->getInverseScaling(); + OSL_ENSURE( m_xInverseScaling.is(), "each Scaling needs to return an inverse Scaling" ); + } + + m_fScaledVisibleMin = m_rScale.Minimum; + if( m_xInverseScaling.is() ) + m_fScaledVisibleMin = m_rScale.Scaling->doScaling(m_fScaledVisibleMin); + + m_fScaledVisibleMax = m_rScale.Maximum; + if( m_xInverseScaling.is() ) + m_fScaledVisibleMax = m_rScale.Scaling->doScaling(m_fScaledVisibleMax); +} + +TickFactory::~TickFactory() +{ +} + +bool TickFactory::isDateAxis() const +{ + return m_rScale.AxisType == chart2::AxisType::DATE; +} + +void TickFactory::getAllTicks( TickInfoArraysType& rAllTickInfos ) const +{ + if( isDateAxis() ) + DateTickFactory( m_rScale, m_rIncrement ).getAllTicks( rAllTickInfos ); + else + EquidistantTickFactory( m_rScale, m_rIncrement ).getAllTicks( rAllTickInfos ); +} + +void TickFactory::getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const +{ + if( isDateAxis() ) + DateTickFactory( m_rScale, m_rIncrement ).getAllTicksShifted( rAllTickInfos ); + else + EquidistantTickFactory( m_rScale, m_rIncrement ).getAllTicksShifted( rAllTickInfos ); +} + +// ___TickFactory_2D___ +TickFactory2D::TickFactory2D( + const ExplicitScaleData& rScale, const ExplicitIncrementData& rIncrement + //, double fStretch_SceneToScreen, double fOffset_SceneToScreen ) + , const B2DVector& rStartScreenPos, const B2DVector& rEndScreenPos + , const B2DVector& rAxisLineToLabelLineShift ) + : TickFactory( rScale, rIncrement ) + , m_aAxisStartScreenPosition2D(rStartScreenPos) + , m_aAxisEndScreenPosition2D(rEndScreenPos) + , m_aAxisLineToLabelLineShift(rAxisLineToLabelLineShift) + , m_fStretch_LogicToScreen(1.0) + , m_fOffset_LogicToScreen(0.0) +{ + double fWidthY = m_fScaledVisibleMax - m_fScaledVisibleMin; + if (m_rScale.Orientation == chart2::AxisOrientation_MATHEMATICAL) + { + m_fStretch_LogicToScreen = 1.0/fWidthY; + m_fOffset_LogicToScreen = -m_fScaledVisibleMin; + } + else + { + B2DVector aSwap(m_aAxisStartScreenPosition2D); + m_aAxisStartScreenPosition2D = m_aAxisEndScreenPosition2D; + m_aAxisEndScreenPosition2D = aSwap; + + m_fStretch_LogicToScreen = -1.0/fWidthY; + m_fOffset_LogicToScreen = -m_fScaledVisibleMax; + } +} + +TickFactory2D::~TickFactory2D() +{ +} + +bool TickFactory2D::isHorizontalAxis() const +{ + // check trivial cases: + if ( m_aAxisStartScreenPosition2D.getY() == m_aAxisEndScreenPosition2D.getY() ) + return true; + if ( m_aAxisStartScreenPosition2D.getX() == m_aAxisEndScreenPosition2D.getX() ) + return false; + + // for skew axes compare angle with horizontal vector + double fInclination = std::abs(B2DVector(m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D).angle(B2DVector(1.0, 0.0))); + return fInclination < M_PI_4 || fInclination > (M_PI-M_PI_4); +} +bool TickFactory2D::isVerticalAxis() const +{ + // check trivial cases: + if ( m_aAxisStartScreenPosition2D.getX() == m_aAxisEndScreenPosition2D.getX() ) + return true; + if ( m_aAxisStartScreenPosition2D.getY() == m_aAxisEndScreenPosition2D.getY() ) + return false; + + // for skew axes compare angle with vertical vector + double fInclination = std::abs(B2DVector(m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D).angle(B2DVector(0.0, -1.0))); + return fInclination < M_PI_4 || fInclination > (M_PI-M_PI_4); +} +//static +sal_Int32 TickFactory2D::getTickScreenDistance( TickIter& rIter ) +{ + //return the positive distance between the two first tickmarks in screen values + //if there are less than two tickmarks -1 is returned + + const TickInfo* pFirstTickInfo = rIter.firstInfo(); + const TickInfo* pSecondTickInfo = rIter.nextInfo(); + if(!pSecondTickInfo || !pFirstTickInfo) + return -1; + + return pFirstTickInfo->getScreenDistanceBetweenTicks( *pSecondTickInfo ); +} + +B2DVector TickFactory2D::getTickScreenPosition2D( double fScaledLogicTickValue ) const +{ + B2DVector aRet(m_aAxisStartScreenPosition2D); + aRet += (m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D) + *((fScaledLogicTickValue+m_fOffset_LogicToScreen)*m_fStretch_LogicToScreen); + return aRet; +} + +void TickFactory2D::addPointSequenceForTickLine( drawing::PointSequenceSequence& rPoints + , sal_Int32 nSequenceIndex + , double fScaledLogicTickValue, double fInnerDirectionSign + , const TickmarkProperties& rTickmarkProperties + , bool bPlaceAtLabels ) const +{ + if( fInnerDirectionSign==0.0 ) + fInnerDirectionSign = 1.0; + + B2DVector aTickScreenPosition = getTickScreenPosition2D(fScaledLogicTickValue); + if( bPlaceAtLabels ) + aTickScreenPosition += m_aAxisLineToLabelLineShift; + + B2DVector aMainDirection = m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D; + aMainDirection.normalize(); + B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX()); + aOrthoDirection *= fInnerDirectionSign; + aOrthoDirection.normalize(); + + B2DVector aStart = aTickScreenPosition + aOrthoDirection*rTickmarkProperties.RelativePos; + B2DVector aEnd = aStart - aOrthoDirection*rTickmarkProperties.Length; + + rPoints.getArray()[nSequenceIndex] + = { { static_cast<sal_Int32>(aStart.getX()), static_cast<sal_Int32>(aStart.getY()) }, + { static_cast<sal_Int32>(aEnd.getX()), static_cast<sal_Int32>(aEnd.getY()) } }; +} + +B2DVector TickFactory2D::getDistanceAxisTickToText( const AxisProperties& rAxisProperties, bool bIncludeFarAwayDistanceIfSo, bool bIncludeSpaceBetweenTickAndText ) const +{ + bool bFarAwayLabels = false; + if( rAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START + || rAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END ) + bFarAwayLabels = true; + + double fInnerDirectionSign = rAxisProperties.maLabelAlignment.mfInnerTickDirection; + if( fInnerDirectionSign==0.0 ) + fInnerDirectionSign = 1.0; + + B2DVector aMainDirection = m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D; + aMainDirection.normalize(); + B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX()); + aOrthoDirection *= fInnerDirectionSign; + aOrthoDirection.normalize(); + + B2DVector aStart(0,0), aEnd(0,0); + if( bFarAwayLabels ) + { + TickmarkProperties aProps( AxisProperties::getBiggestTickmarkProperties() ); + aStart = aOrthoDirection*aProps.RelativePos; + aEnd = aStart - aOrthoDirection*aProps.Length; + } + else + { + for( sal_Int32 nN=rAxisProperties.m_aTickmarkPropertiesList.size();nN--;) + { + const TickmarkProperties& rProps = rAxisProperties.m_aTickmarkPropertiesList[nN]; + B2DVector aNewStart = aOrthoDirection*rProps.RelativePos; + B2DVector aNewEnd = aNewStart - aOrthoDirection*rProps.Length; + if(aNewStart.getLength()>aStart.getLength()) + aStart=aNewStart; + if(aNewEnd.getLength()>aEnd.getLength()) + aEnd=aNewEnd; + } + } + + B2DVector aLabelDirection(aStart); + if (rAxisProperties.maLabelAlignment.mfInnerTickDirection != rAxisProperties.maLabelAlignment.mfLabelDirection) + aLabelDirection = aEnd; + + B2DVector aOrthoLabelDirection(aOrthoDirection); + if (rAxisProperties.maLabelAlignment.mfInnerTickDirection != rAxisProperties.maLabelAlignment.mfLabelDirection) + aOrthoLabelDirection*=-1.0; + aOrthoLabelDirection.normalize(); + if( bIncludeSpaceBetweenTickAndText ) + aLabelDirection += aOrthoLabelDirection*AXIS2D_TICKLABELSPACING; + if( bFarAwayLabels && bIncludeFarAwayDistanceIfSo ) + aLabelDirection += m_aAxisLineToLabelLineShift; + return aLabelDirection; +} + +void TickFactory2D::createPointSequenceForAxisMainLine( drawing::PointSequenceSequence& rPoints ) const +{ + rPoints.getArray()[0] = { { static_cast<sal_Int32>(m_aAxisStartScreenPosition2D.getX()), + static_cast<sal_Int32>(m_aAxisStartScreenPosition2D.getY()) }, + { static_cast<sal_Int32>(m_aAxisEndScreenPosition2D.getX()), + static_cast<sal_Int32>(m_aAxisEndScreenPosition2D.getY()) } }; +} + +void TickFactory2D::updateScreenValues( TickInfoArraysType& rAllTickInfos ) const +{ + //get the transformed screen values for all tickmarks in rAllTickInfos + for (auto & tickInfos : rAllTickInfos) + { + for (auto & tickInfo : tickInfos) + { + tickInfo.aTickScreenPosition = + getTickScreenPosition2D(tickInfo.fScaledTickValue); + } + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/Tickmarks.hxx b/chart2/source/view/axes/Tickmarks.hxx new file mode 100644 index 000000000..bb33be504 --- /dev/null +++ b/chart2/source/view/axes/Tickmarks.hxx @@ -0,0 +1,162 @@ +/* -*- 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 . + */ +#pragma once + +#include <chartview/ExplicitScaleValues.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <rtl/ref.hxx> +#include <svx/unoshape.hxx> +#include <vector> + +namespace chart { struct AxisProperties; } +namespace chart { struct TickmarkProperties; } +namespace com::sun::star::chart2 { class XScaling; } +namespace com::sun::star::drawing { class XShape; } + +namespace chart { + +struct TickInfo +{ + double fScaledTickValue; + css::uno::Reference<css::chart2::XScaling> xInverseScaling; + rtl::Reference<SvxShapeText> xTextShape; + OUString aText;//used only for complex categories so far + ::basegfx::B2DVector aTickScreenPosition; + sal_Int32 nFactorForLimitedTextWidth;//categories in higher levels of complex categories can have more place than a single simple category + bool bPaintIt; + +//methods: + TickInfo() = delete; + explicit TickInfo( const css::uno::Reference<css::chart2::XScaling>& xInverse ); + + /** + * Return a value associated with the tick mark. It's normally an original + * value from the data source, or 1-based integer index in case the axis + * is a category axis. + */ + double getUnscaledTickValue() const; + sal_Int32 getScreenDistanceBetweenTicks( const TickInfo& rOherTickInfo ) const; +}; + +typedef std::vector<TickInfo> TickInfoArrayType; +typedef std::vector<TickInfoArrayType> TickInfoArraysType; + +class TickIter +{ +public: + virtual ~TickIter() {} + virtual TickInfo* firstInfo() = 0; + virtual TickInfo* nextInfo() = 0; +}; + +class PureTickIter : public TickIter +{ +public: + explicit PureTickIter( TickInfoArrayType& rTickInfoVector ); + virtual ~PureTickIter() override; + virtual TickInfo* firstInfo() override; + virtual TickInfo* nextInfo() override; + +private: + TickInfoArrayType& m_rTickVector; + TickInfoArrayType::iterator m_aTickIter; +}; + +class TickFactory +{ +public: + TickFactory( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ); + virtual ~TickFactory(); + + void getAllTicks( TickInfoArraysType& rAllTickInfos ) const; + void getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const; + +private: //methods + bool isDateAxis() const; + +protected: //member + ExplicitScaleData m_rScale; + ExplicitIncrementData m_rIncrement; + css::uno::Reference< css::chart2::XScaling > m_xInverseScaling; + + //minimum and maximum of the visible range after scaling + double m_fScaledVisibleMin; + double m_fScaledVisibleMax; +}; + +class TickFactory2D final : public TickFactory +{ +public: + TickFactory2D( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement + , const ::basegfx::B2DVector& rStartScreenPos, const ::basegfx::B2DVector& rEndScreenPos + , const ::basegfx::B2DVector& rAxisLineToLabelLineShift ); + + virtual ~TickFactory2D() override; + + static sal_Int32 getTickScreenDistance( TickIter& rIter ); + + void createPointSequenceForAxisMainLine( css::drawing::PointSequenceSequence& rPoints ) const; + void addPointSequenceForTickLine( css::drawing::PointSequenceSequence& rPoints + , sal_Int32 nSequenceIndex + , double fScaledLogicTickValue, double fInnerDirectionSign + , const TickmarkProperties& rTickmarkProperties, bool bPlaceAtLabels ) const; + ::basegfx::B2DVector getDistanceAxisTickToText( const AxisProperties& rAxisProperties + , bool bIncludeFarAwayDistanceIfSo = false + , bool bIncludeSpaceBetweenTickAndText = true ) const; + + /** + * Determine the screen positions of all ticks based on their numeric values. + */ + void updateScreenValues( TickInfoArraysType& rAllTickInfos ) const; + + bool isHorizontalAxis() const; + bool isVerticalAxis() const; + + const ::basegfx::B2DVector & getXaxisStartPos() const + { + return m_aAxisStartScreenPosition2D; + } + + const ::basegfx::B2DVector & getXaxisEndPos() const + { + return m_aAxisEndScreenPosition2D; + } + +private: + ::basegfx::B2DVector getTickScreenPosition2D( double fScaledLogicTickValue ) const; + + ::basegfx::B2DVector m_aAxisStartScreenPosition2D; + ::basegfx::B2DVector m_aAxisEndScreenPosition2D; + + //labels might be positioned high or low on the border of the diagram far away from the axis + //add this vector to go from the axis line to the label line (border of the diagram) + ::basegfx::B2DVector m_aAxisLineToLabelLineShift; + + double m_fStretch_LogicToScreen; + double m_fOffset_LogicToScreen; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/Tickmarks_Dates.cxx b/chart2/source/view/axes/Tickmarks_Dates.cxx new file mode 100644 index 000000000..854e661f6 --- /dev/null +++ b/chart2/source/view/axes/Tickmarks_Dates.cxx @@ -0,0 +1,150 @@ +/* -*- 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 "Tickmarks_Dates.hxx" +#include "DateScaling.hxx" +#include <rtl/math.hxx> +#include <osl/diagnose.h> +#include <DateHelper.hxx> +#include <com/sun/star/chart/TimeUnit.hpp> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using namespace ::rtl::math; +using ::com::sun::star::chart::TimeUnit::DAY; +using ::com::sun::star::chart::TimeUnit::MONTH; +using ::com::sun::star::chart::TimeUnit::YEAR; + +DateTickFactory::DateTickFactory( + const ExplicitScaleData& rScale, const ExplicitIncrementData& rIncrement ) + : m_aScale( rScale ) + , m_aIncrement( rIncrement ) +{ + //@todo: make sure that the scale is valid for the scaling + + if( m_aScale.Scaling.is() ) + { + m_xInverseScaling = m_aScale.Scaling->getInverseScaling(); + OSL_ENSURE( m_xInverseScaling.is(), "each Scaling needs to return an inverse Scaling" ); + } +} + +DateTickFactory::~DateTickFactory() +{ +} + +void DateTickFactory::getAllTicks( TickInfoArraysType& rAllTickInfos, bool bShifted ) const +{ + rAllTickInfos.resize(2); + TickInfoArrayType& rMajorTicks = rAllTickInfos[0]; + TickInfoArrayType& rMinorTicks = rAllTickInfos[1]; + rMajorTicks.clear(); + rMinorTicks.clear(); + + Date aNull(m_aScale.NullDate); + + Date aDate = aNull + static_cast<sal_Int32>(::rtl::math::approxFloor(m_aScale.Minimum)); + Date aMaxDate = aNull + static_cast<sal_Int32>(::rtl::math::approxFloor(m_aScale.Maximum)); + + uno::Reference< chart2::XScaling > xScaling(m_aScale.Scaling); + uno::Reference< chart2::XScaling > xInverseScaling(m_xInverseScaling); + if( bShifted ) + { + xScaling = new DateScaling(aNull,m_aScale.TimeResolution,true/*bShifted*/); + xInverseScaling = xScaling->getInverseScaling(); + } + + //create major date tickinfos + while( aDate<= aMaxDate ) + { + if( bShifted && aDate==aMaxDate ) + break; + + TickInfo aNewTick(xInverseScaling); aNewTick.fScaledTickValue = aDate - aNull; + + if( xInverseScaling.is() ) + aNewTick.fScaledTickValue = xScaling->doScaling(aNewTick.fScaledTickValue); + rMajorTicks.push_back( aNewTick ); + + if(m_aIncrement.MajorTimeInterval.Number<=0) + break; + + //find next major date + switch( m_aIncrement.MajorTimeInterval.TimeUnit ) + { + case DAY: + aDate.AddDays( m_aIncrement.MajorTimeInterval.Number ); + break; + case YEAR: + aDate = DateHelper::GetDateSomeYearsAway( aDate, m_aIncrement.MajorTimeInterval.Number ); + break; + case MONTH: + default: + aDate = DateHelper::GetDateSomeMonthsAway( aDate, m_aIncrement.MajorTimeInterval.Number ); + break; + } + } + + //create minor date tickinfos + aDate = aNull + static_cast<sal_Int32>(::rtl::math::approxFloor(m_aScale.Minimum)); + while( aDate<= aMaxDate ) + { + if( bShifted && aDate==aMaxDate ) + break; + + TickInfo aNewTick(xInverseScaling); aNewTick.fScaledTickValue = aDate - aNull; + if( xInverseScaling.is() ) + aNewTick.fScaledTickValue = xScaling->doScaling(aNewTick.fScaledTickValue); + rMinorTicks.push_back( aNewTick ); + + if(m_aIncrement.MinorTimeInterval.Number<=0) + break; + + //find next minor date + switch( m_aIncrement.MinorTimeInterval.TimeUnit ) + { + case DAY: + aDate.AddDays( m_aIncrement.MinorTimeInterval.Number ); + break; + case YEAR: + aDate = DateHelper::GetDateSomeYearsAway( aDate, m_aIncrement.MinorTimeInterval.Number ); + break; + case MONTH: + default: + aDate = DateHelper::GetDateSomeMonthsAway( aDate, m_aIncrement.MinorTimeInterval.Number ); + break; + } + } +} + +void DateTickFactory::getAllTicks( TickInfoArraysType& rAllTickInfos ) const +{ + getAllTicks( rAllTickInfos, false ); +} + +void DateTickFactory::getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const +{ + getAllTicks( rAllTickInfos, true ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/Tickmarks_Dates.hxx b/chart2/source/view/axes/Tickmarks_Dates.hxx new file mode 100644 index 000000000..0a2140192 --- /dev/null +++ b/chart2/source/view/axes/Tickmarks_Dates.hxx @@ -0,0 +1,49 @@ +/* -*- 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 . + */ +#pragma once + +#include "Tickmarks.hxx" + +namespace chart +{ + +class DateTickFactory +{ +public: + DateTickFactory( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ); + ~DateTickFactory(); + + void getAllTicks( TickInfoArraysType& rAllTickInfos ) const; + void getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const; + +private: //methods + void getAllTicks( TickInfoArraysType& rAllTickInfos, bool bShifted ) const; + +private: //member + ExplicitScaleData m_aScale; + ExplicitIncrementData m_aIncrement; + css::uno::Reference< css::chart2::XScaling > + m_xInverseScaling; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/Tickmarks_Equidistant.cxx b/chart2/source/view/axes/Tickmarks_Equidistant.cxx new file mode 100644 index 000000000..11778d694 --- /dev/null +++ b/chart2/source/view/axes/Tickmarks_Equidistant.cxx @@ -0,0 +1,625 @@ +/* -*- 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 "Tickmarks_Equidistant.hxx" +#include <rtl/math.hxx> +#include <osl/diagnose.h> +#include <float.h> + +#include <limits> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using namespace ::rtl::math; + +//static +double EquidistantTickFactory::getMinimumAtIncrement( double fMin, const ExplicitIncrementData& rIncrement ) +{ + //the returned value will be <= fMin and on a Major Tick given by rIncrement + if(rIncrement.Distance<=0.0) + return fMin; + + double fRet = rIncrement.BaseValue + + floor( approxSub( fMin, rIncrement.BaseValue ) + / rIncrement.Distance) + *rIncrement.Distance; + + if( fRet > fMin ) + { + if( !approxEqual(fRet, fMin) ) + fRet -= rIncrement.Distance; + } + return fRet; +} +//static +double EquidistantTickFactory::getMaximumAtIncrement( double fMax, const ExplicitIncrementData& rIncrement ) +{ + //the returned value will be >= fMax and on a Major Tick given by rIncrement + if(rIncrement.Distance<=0.0) + return fMax; + + double fRet = rIncrement.BaseValue + + floor( approxSub( fMax, rIncrement.BaseValue ) + / rIncrement.Distance) + *rIncrement.Distance; + + if( fRet < fMax ) + { + if( !approxEqual(fRet, fMax) ) + fRet += rIncrement.Distance; + } + return fRet; +} + +EquidistantTickFactory::EquidistantTickFactory( + const ExplicitScaleData& rScale, const ExplicitIncrementData& rIncrement ) + : m_rScale( rScale ) + , m_rIncrement( rIncrement ) +{ + //@todo: make sure that the scale is valid for the scaling + + m_pfCurrentValues.reset( new double[getTickDepth()] ); + + if( m_rScale.Scaling.is() ) + { + m_xInverseScaling = m_rScale.Scaling->getInverseScaling(); + OSL_ENSURE( m_xInverseScaling.is(), "each Scaling needs to return an inverse Scaling" ); + } + + double fMin = m_fScaledVisibleMin = m_rScale.Minimum; + if( m_xInverseScaling.is() ) + { + m_fScaledVisibleMin = m_rScale.Scaling->doScaling(m_fScaledVisibleMin); + if(m_rIncrement.PostEquidistant ) + fMin = m_fScaledVisibleMin; + } + + double fMax = m_fScaledVisibleMax = m_rScale.Maximum; + if( m_xInverseScaling.is() ) + { + m_fScaledVisibleMax = m_rScale.Scaling->doScaling(m_fScaledVisibleMax); + if(m_rIncrement.PostEquidistant ) + fMax = m_fScaledVisibleMax; + } + + m_fOuterMajorTickBorderMin = EquidistantTickFactory::getMinimumAtIncrement( fMin, m_rIncrement ); + m_fOuterMajorTickBorderMax = EquidistantTickFactory::getMaximumAtIncrement( fMax, m_rIncrement ); + + m_fOuterMajorTickBorderMin_Scaled = m_fOuterMajorTickBorderMin; + m_fOuterMajorTickBorderMax_Scaled = m_fOuterMajorTickBorderMax; + if(m_rIncrement.PostEquidistant || !m_xInverseScaling.is()) + return; + + m_fOuterMajorTickBorderMin_Scaled = m_rScale.Scaling->doScaling(m_fOuterMajorTickBorderMin); + m_fOuterMajorTickBorderMax_Scaled = m_rScale.Scaling->doScaling(m_fOuterMajorTickBorderMax); + + //check validity of new range: m_fOuterMajorTickBorderMin <-> m_fOuterMajorTickBorderMax + //it is assumed here, that the original range in the given Scale is valid + if( !std::isfinite(m_fOuterMajorTickBorderMin_Scaled) ) + { + m_fOuterMajorTickBorderMin += m_rIncrement.Distance; + m_fOuterMajorTickBorderMin_Scaled = m_rScale.Scaling->doScaling(m_fOuterMajorTickBorderMin); + } + if( !std::isfinite(m_fOuterMajorTickBorderMax_Scaled) ) + { + m_fOuterMajorTickBorderMax -= m_rIncrement.Distance; + m_fOuterMajorTickBorderMax_Scaled = m_rScale.Scaling->doScaling(m_fOuterMajorTickBorderMax); + } +} + +EquidistantTickFactory::~EquidistantTickFactory() +{ +} + +sal_Int32 EquidistantTickFactory::getTickDepth() const +{ + return static_cast<sal_Int32>(m_rIncrement.SubIncrements.size()) + 1; +} + +void EquidistantTickFactory::addSubTicks( sal_Int32 nDepth, uno::Sequence< uno::Sequence< double > >& rParentTicks ) const +{ + EquidistantTickIter aIter( rParentTicks, m_rIncrement, nDepth-1 ); + double* pfNextParentTick = aIter.firstValue(); + if(!pfNextParentTick) + return; + double fLastParentTick = *pfNextParentTick; + pfNextParentTick = aIter.nextValue(); + if(!pfNextParentTick) + return; + + sal_Int32 nMaxSubTickCount = getMaxTickCount( nDepth ); + if(!nMaxSubTickCount) + return; + + uno::Sequence< double > aSubTicks(nMaxSubTickCount); + auto pSubTicks = aSubTicks.getArray(); + sal_Int32 nRealSubTickCount = 0; + sal_Int32 nIntervalCount = m_rIncrement.SubIncrements[nDepth-1].IntervalCount; + + double* pValue = nullptr; + for(; pfNextParentTick; fLastParentTick=*pfNextParentTick, pfNextParentTick = aIter.nextValue()) + { + for( sal_Int32 nPartTick = 1; nPartTick<nIntervalCount; nPartTick++ ) + { + pValue = getMinorTick( nPartTick, nDepth + , fLastParentTick, *pfNextParentTick ); + if(!pValue) + continue; + + pSubTicks[nRealSubTickCount] = *pValue; + nRealSubTickCount++; + } + } + + aSubTicks.realloc(nRealSubTickCount); + rParentTicks.getArray()[nDepth] = aSubTicks; + if(static_cast<sal_Int32>(m_rIncrement.SubIncrements.size())>nDepth) + addSubTicks( nDepth+1, rParentTicks ); +} + +sal_Int32 EquidistantTickFactory::getMaxTickCount( sal_Int32 nDepth ) const +{ + //return the maximum amount of ticks + //possibly open intervals at the two ends of the region are handled as if they were completely visible + //(this is necessary for calculating the sub ticks at the borders correctly) + + if( nDepth >= getTickDepth() ) + return 0; + if( m_fOuterMajorTickBorderMax < m_fOuterMajorTickBorderMin ) + return 0; + if( m_rIncrement.Distance<=0.0) + return 0; + + double fSub; + if(m_rIncrement.PostEquidistant ) + fSub = approxSub( m_fScaledVisibleMax, m_fScaledVisibleMin ); + else + fSub = approxSub( m_rScale.Maximum, m_rScale.Minimum ); + + if (!std::isfinite(fSub)) + return 0; + + double fIntervalCount = fSub / m_rIncrement.Distance; + if (fIntervalCount > std::numeric_limits<sal_Int32>::max()) + // Interval count too high! Bail out. + return 0; + + sal_Int32 nIntervalCount = static_cast<sal_Int32>(fIntervalCount); + + nIntervalCount+=3; + for(sal_Int32 nN=0; nN<nDepth-1; nN++) + { + if( m_rIncrement.SubIncrements[nN].IntervalCount>1 ) + nIntervalCount *= m_rIncrement.SubIncrements[nN].IntervalCount; + } + + sal_Int32 nTickCount = nIntervalCount; + if(nDepth>0 && m_rIncrement.SubIncrements[nDepth-1].IntervalCount>1) + nTickCount = nIntervalCount * (m_rIncrement.SubIncrements[nDepth-1].IntervalCount-1); + + return nTickCount; +} + +double* EquidistantTickFactory::getMajorTick( sal_Int32 nTick ) const +{ + m_pfCurrentValues[0] = m_fOuterMajorTickBorderMin + nTick*m_rIncrement.Distance; + + if(m_pfCurrentValues[0]>m_fOuterMajorTickBorderMax) + { + if( !approxEqual(m_pfCurrentValues[0],m_fOuterMajorTickBorderMax) ) + return nullptr; + } + if(m_pfCurrentValues[0]<m_fOuterMajorTickBorderMin) + { + if( !approxEqual(m_pfCurrentValues[0],m_fOuterMajorTickBorderMin) ) + return nullptr; + } + + //return always the value after scaling + if(!m_rIncrement.PostEquidistant && m_xInverseScaling.is() ) + m_pfCurrentValues[0] = m_rScale.Scaling->doScaling( m_pfCurrentValues[0] ); + + return &m_pfCurrentValues[0]; +} + +double* EquidistantTickFactory::getMinorTick( sal_Int32 nTick, sal_Int32 nDepth + , double fStartParentTick, double fNextParentTick ) const +{ + //check validity of arguments + { + //OSL_ENSURE( fStartParentTick < fNextParentTick, "fStartParentTick >= fNextParentTick"); + if(fStartParentTick >= fNextParentTick) + return nullptr; + if(nDepth>static_cast<sal_Int32>(m_rIncrement.SubIncrements.size()) || nDepth<=0) + return nullptr; + + //subticks are only calculated if they are laying between parent ticks: + if(nTick<=0) + return nullptr; + if(nTick>=m_rIncrement.SubIncrements[nDepth-1].IntervalCount) + return nullptr; + } + + bool bPostEquidistant = m_rIncrement.SubIncrements[nDepth-1].PostEquidistant; + + double fAdaptedStartParent = fStartParentTick; + double fAdaptedNextParent = fNextParentTick; + + if( !bPostEquidistant && m_xInverseScaling.is() ) + { + fAdaptedStartParent = m_xInverseScaling->doScaling(fStartParentTick); + fAdaptedNextParent = m_xInverseScaling->doScaling(fNextParentTick); + } + + double fDistance = (fAdaptedNextParent - fAdaptedStartParent)/m_rIncrement.SubIncrements[nDepth-1].IntervalCount; + + m_pfCurrentValues[nDepth] = fAdaptedStartParent + nTick*fDistance; + + //return always the value after scaling + if(!bPostEquidistant && m_xInverseScaling.is() ) + m_pfCurrentValues[nDepth] = m_rScale.Scaling->doScaling( m_pfCurrentValues[nDepth] ); + + if( !isWithinOuterBorder( m_pfCurrentValues[nDepth] ) ) + return nullptr; + + return &m_pfCurrentValues[nDepth]; +} + +bool EquidistantTickFactory::isWithinOuterBorder( double fScaledValue ) const +{ + if(fScaledValue>m_fOuterMajorTickBorderMax_Scaled) + return false; + if(fScaledValue<m_fOuterMajorTickBorderMin_Scaled) + return false; + + return true; +} + +bool EquidistantTickFactory::isVisible( double fScaledValue ) const +{ + if(fScaledValue>m_fScaledVisibleMax) + { + if( !approxEqual(fScaledValue,m_fScaledVisibleMax) ) + return false; + } + if(fScaledValue<m_fScaledVisibleMin) + { + if( !approxEqual(fScaledValue,m_fScaledVisibleMin) ) + return false; + } + return true; +} + +void EquidistantTickFactory::getAllTicks( TickInfoArraysType& rAllTickInfos ) const +{ + //create point sequences for each tick depth + const sal_Int32 nDepthCount = getTickDepth(); + const sal_Int32 nMaxMajorTickCount = getMaxTickCount(0); + + if (nDepthCount <= 0 || nMaxMajorTickCount <= 0) + return; + + uno::Sequence< uno::Sequence< double > > aAllTicks(nDepthCount); + auto pAllTicks = aAllTicks.getArray(); + pAllTicks[0].realloc(nMaxMajorTickCount); + auto pAllTicks0 = pAllTicks[0].getArray(); + + sal_Int32 nRealMajorTickCount = 0; + for( sal_Int32 nMajorTick=0; nMajorTick<nMaxMajorTickCount; nMajorTick++ ) + { + double* pValue = getMajorTick( nMajorTick ); + if(!pValue) + continue; + pAllTicks0[nRealMajorTickCount] = *pValue; + nRealMajorTickCount++; + } + if(!nRealMajorTickCount) + return; + pAllTicks[0].realloc(nRealMajorTickCount); + + addSubTicks(1, aAllTicks); + + //so far we have added all ticks between the outer major tick marks + //this was necessary to create sub ticks correctly + //now we reduce all ticks to the visible ones that lie between the real borders + sal_Int32 nDepth = 0; + sal_Int32 nTick = 0; + for( nDepth = 0; nDepth < nDepthCount; nDepth++) + { + sal_Int32 nInvisibleAtLowerBorder = 0; + sal_Int32 nInvisibleAtUpperBorder = 0; + //we need only to check all ticks within the first major interval at each border + sal_Int32 nCheckCount = 1; + for(sal_Int32 nN=0; nN<nDepth; nN++) + { + if( m_rIncrement.SubIncrements[nN].IntervalCount>1 ) + nCheckCount *= m_rIncrement.SubIncrements[nN].IntervalCount; + } + uno::Sequence< double >& rTicks = pAllTicks[nDepth]; + sal_Int32 nCount = rTicks.getLength(); + //check lower border + for( nTick=0; nTick<nCheckCount && nTick<nCount; nTick++) + { + if( !isVisible( rTicks[nTick] ) ) + nInvisibleAtLowerBorder++; + } + //check upper border + for( nTick=nCount-1; nTick>nCount-1-nCheckCount && nTick>=0; nTick--) + { + if( !isVisible( rTicks[nTick] ) ) + nInvisibleAtUpperBorder++; + } + //resize sequence + if( !nInvisibleAtLowerBorder && !nInvisibleAtUpperBorder) + continue; + if( !nInvisibleAtLowerBorder ) + rTicks.realloc(nCount-nInvisibleAtUpperBorder); + else + { + sal_Int32 nNewCount = nCount-nInvisibleAtUpperBorder-nInvisibleAtLowerBorder; + if(nNewCount<0) + nNewCount=0; + + uno::Sequence< double > aOldTicks(rTicks); + rTicks.realloc(nNewCount); + auto pTicks = rTicks.getArray(); + for(nTick = 0; nTick<nNewCount; nTick++) + pTicks[nTick] = aOldTicks[nInvisibleAtLowerBorder+nTick]; + } + } + + //fill return value + rAllTickInfos.resize(aAllTicks.getLength()); + for( nDepth=0 ;nDepth<aAllTicks.getLength(); nDepth++ ) + { + sal_Int32 nCount = aAllTicks[nDepth].getLength(); + + TickInfoArrayType& rTickInfoVector = rAllTickInfos[nDepth]; + rTickInfoVector.clear(); + rTickInfoVector.reserve( nCount ); + for(sal_Int32 nN = 0; nN<nCount; nN++) + { + TickInfo aTickInfo(m_xInverseScaling); + aTickInfo.fScaledTickValue = aAllTicks[nDepth][nN]; + rTickInfoVector.push_back(aTickInfo); + } + } +} + +void EquidistantTickFactory::getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const +{ + ExplicitIncrementData aShiftedIncrement( m_rIncrement ); + aShiftedIncrement.BaseValue = m_rIncrement.BaseValue-m_rIncrement.Distance/2.0; + EquidistantTickFactory( m_rScale, aShiftedIncrement ).getAllTicks(rAllTickInfos); +} + +EquidistantTickIter::EquidistantTickIter( const uno::Sequence< uno::Sequence< double > >& rTicks + , const ExplicitIncrementData& rIncrement + , sal_Int32 nMaxDepth ) + : m_pSimpleTicks(&rTicks) + , m_pInfoTicks(nullptr) + , m_rIncrement(rIncrement) + , m_nMaxDepth(0) + , m_nTickCount(0) + , m_nCurrentDepth(-1), m_nCurrentPos(-1), m_fCurrentValue( 0.0 ) +{ + initIter( nMaxDepth ); +} + +EquidistantTickIter::EquidistantTickIter( TickInfoArraysType& rTicks + , const ExplicitIncrementData& rIncrement + , sal_Int32 nMaxDepth ) + : m_pSimpleTicks(nullptr) + , m_pInfoTicks(&rTicks) + , m_rIncrement(rIncrement) + , m_nMaxDepth(0) + , m_nTickCount(0) + , m_nCurrentDepth(-1), m_nCurrentPos(-1), m_fCurrentValue( 0.0 ) +{ + initIter( nMaxDepth ); +} + +void EquidistantTickIter::initIter( sal_Int32 nMaxDepth ) +{ + m_nMaxDepth = nMaxDepth; + if(nMaxDepth<0 || m_nMaxDepth>getMaxDepth()) + m_nMaxDepth=getMaxDepth(); + + sal_Int32 nDepth = 0; + for( nDepth = 0; nDepth<=m_nMaxDepth ;nDepth++ ) + m_nTickCount += getTickCount(nDepth); + + if(!m_nTickCount) + return; + + m_pnPositions.reset( new sal_Int32[m_nMaxDepth+1] ); + + m_pnPreParentCount.reset( new sal_Int32[m_nMaxDepth+1] ); + m_pbIntervalFinished.reset( new bool[m_nMaxDepth+1] ); + m_pnPreParentCount[0] = 0; + m_pbIntervalFinished[0] = false; + double fParentValue = getTickValue(0,0); + for( nDepth = 1; nDepth<=m_nMaxDepth ;nDepth++ ) + { + m_pbIntervalFinished[nDepth] = false; + + sal_Int32 nPreParentCount = 0; + sal_Int32 nCount = getTickCount(nDepth); + for(sal_Int32 nN = 0; nN<nCount; nN++) + { + if(getTickValue(nDepth,nN) < fParentValue) + nPreParentCount++; + else + break; + } + m_pnPreParentCount[nDepth] = nPreParentCount; + if(nCount) + { + double fNextParentValue = getTickValue(nDepth,0); + if( fNextParentValue < fParentValue ) + fParentValue = fNextParentValue; + } + } +} + +EquidistantTickIter::~EquidistantTickIter() +{ +} + +sal_Int32 EquidistantTickIter::getStartDepth() const +{ + //find the depth of the first visible tickmark: + //it is the depth of the smallest value + sal_Int32 nReturnDepth=0; + double fMinValue = DBL_MAX; + for(sal_Int32 nDepth = 0; nDepth<=m_nMaxDepth ;nDepth++ ) + { + sal_Int32 nCount = getTickCount(nDepth); + if( !nCount ) + continue; + double fThisValue = getTickValue(nDepth,0); + if(fThisValue<fMinValue) + { + nReturnDepth = nDepth; + fMinValue = fThisValue; + } + } + return nReturnDepth; +} + +double* EquidistantTickIter::firstValue() +{ + if( gotoFirst() ) + { + m_fCurrentValue = getTickValue(m_nCurrentDepth, m_pnPositions[m_nCurrentDepth]); + return &m_fCurrentValue; + } + return nullptr; +} + +TickInfo* EquidistantTickIter::firstInfo() +{ + if( m_pInfoTicks && gotoFirst() ) + return &(*m_pInfoTicks)[m_nCurrentDepth][m_pnPositions[m_nCurrentDepth]]; + return nullptr; +} + +sal_Int32 EquidistantTickIter::getIntervalCount( sal_Int32 nDepth ) +{ + if(nDepth>static_cast<sal_Int32>(m_rIncrement.SubIncrements.size()) || nDepth<0) + return 0; + + if(!nDepth) + return m_nTickCount; + + return m_rIncrement.SubIncrements[nDepth-1].IntervalCount; +} + +bool EquidistantTickIter::isAtLastPartTick() +{ + if(!m_nCurrentDepth) + return false; + sal_Int32 nIntervalCount = getIntervalCount( m_nCurrentDepth ); + if(!nIntervalCount || nIntervalCount == 1) + return true; + if( m_pbIntervalFinished[m_nCurrentDepth] ) + return false; + sal_Int32 nPos = m_pnPositions[m_nCurrentDepth]+1; + if(m_pnPreParentCount[m_nCurrentDepth]) + nPos += nIntervalCount-1 - m_pnPreParentCount[m_nCurrentDepth]; + bool bRet = nPos && nPos % (nIntervalCount-1) == 0; + if(!nPos && !m_pnPreParentCount[m_nCurrentDepth] + && m_pnPositions[m_nCurrentDepth-1]==-1 ) + bRet = true; + return bRet; +} + +bool EquidistantTickIter::gotoFirst() +{ + if( m_nMaxDepth<0 ) + return false; + if( !m_nTickCount ) + return false; + + for(sal_Int32 nDepth = 0; nDepth<=m_nMaxDepth ;nDepth++ ) + m_pnPositions[nDepth] = -1; + + m_nCurrentPos = 0; + m_nCurrentDepth = getStartDepth(); + m_pnPositions[m_nCurrentDepth] = 0; + return true; +} + +bool EquidistantTickIter::gotoNext() +{ + if( m_nCurrentPos < 0 ) + return false; + m_nCurrentPos++; + + if( m_nCurrentPos >= m_nTickCount ) + return false; + + if( m_nCurrentDepth==m_nMaxDepth && isAtLastPartTick() ) + { + do + { + m_pbIntervalFinished[m_nCurrentDepth] = true; + m_nCurrentDepth--; + } + while( m_nCurrentDepth && isAtLastPartTick() ); + } + else if( m_nCurrentDepth<m_nMaxDepth ) + { + do + { + m_nCurrentDepth++; + } + while( m_nCurrentDepth<m_nMaxDepth ); + } + m_pbIntervalFinished[m_nCurrentDepth] = false; + m_pnPositions[m_nCurrentDepth] = m_pnPositions[m_nCurrentDepth]+1; + return true; +} + +double* EquidistantTickIter::nextValue() +{ + if( gotoNext() ) + { + m_fCurrentValue = getTickValue(m_nCurrentDepth, m_pnPositions[m_nCurrentDepth]); + return &m_fCurrentValue; + } + return nullptr; +} + +TickInfo* EquidistantTickIter::nextInfo() +{ + if( m_pInfoTicks && gotoNext() && + static_cast< sal_Int32 >( + (*m_pInfoTicks)[m_nCurrentDepth].size()) > m_pnPositions[m_nCurrentDepth] ) + { + return &(*m_pInfoTicks)[m_nCurrentDepth][m_pnPositions[m_nCurrentDepth]]; + } + return nullptr; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/Tickmarks_Equidistant.hxx b/chart2/source/view/axes/Tickmarks_Equidistant.hxx new file mode 100644 index 000000000..bb6553339 --- /dev/null +++ b/chart2/source/view/axes/Tickmarks_Equidistant.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ +#pragma once + +#include "Tickmarks.hxx" +#include <memory> + +#include <o3tl/safeint.hxx> + +namespace chart +{ + +class EquidistantTickIter : public TickIter +{ +public: + EquidistantTickIter( const css::uno::Sequence< css::uno::Sequence< double > >& rTicks + , const ExplicitIncrementData& rIncrement + , sal_Int32 nMaxDepth ); + EquidistantTickIter( TickInfoArraysType& rTickInfos + , const ExplicitIncrementData& rIncrement + , sal_Int32 nMaxDepth ); + virtual ~EquidistantTickIter() override; + + double* firstValue(); + double* nextValue(); + + virtual TickInfo* firstInfo() override; + virtual TickInfo* nextInfo() override; + +private: //methods + sal_Int32 getIntervalCount( sal_Int32 nDepth ); + bool isAtLastPartTick(); + + void initIter( sal_Int32 nMaxDepth ); + sal_Int32 getStartDepth() const; + + bool gotoFirst(); + bool gotoNext(); + + double getTickValue(sal_Int32 nDepth, sal_Int32 nIndex) const + { + if(m_pSimpleTicks) + return (*m_pSimpleTicks)[nDepth][nIndex]; + else + { + if ((*m_pInfoTicks)[nDepth].size() <= o3tl::make_unsigned(nIndex)) + return std::numeric_limits<double>::max(); + return (((*m_pInfoTicks)[nDepth])[nIndex]).fScaledTickValue; + } + } + sal_Int32 getTickCount( sal_Int32 nDepth ) const + { + if(m_pSimpleTicks) + return (*m_pSimpleTicks)[nDepth].getLength(); + else + return (*m_pInfoTicks)[nDepth].size(); + } + sal_Int32 getMaxDepth() const + { + if(m_pSimpleTicks) + return (*m_pSimpleTicks).getLength()-1; + else + return (*m_pInfoTicks).size()-1; + } + +private: //member + const css::uno::Sequence< css::uno::Sequence< double > >* m_pSimpleTicks; + TickInfoArraysType* m_pInfoTicks; + const ExplicitIncrementData& m_rIncrement; + sal_Int32 m_nMaxDepth; + sal_Int32 m_nTickCount; + std::unique_ptr<sal_Int32[]> + m_pnPositions; //current positions in the different sequences + std::unique_ptr<sal_Int32[]> + m_pnPreParentCount; //the tickmarks do not start with a major tick always, + //the PreParentCount states for each depth how many subtickmarks are available in front of the first parent tickmark + std::unique_ptr<bool[]> + m_pbIntervalFinished; + sal_Int32 m_nCurrentDepth; + sal_Int32 m_nCurrentPos; + double m_fCurrentValue; +}; + +class EquidistantTickFactory +{ +public: + EquidistantTickFactory( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ); + ~EquidistantTickFactory(); + + void getAllTicks( TickInfoArraysType& rAllTickInfos ) const; + void getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const; + + static double getMinimumAtIncrement( double fMin, const ExplicitIncrementData& rIncrement ); + static double getMaximumAtIncrement( double fMax, const ExplicitIncrementData& rIncrement ); + +private: //methods + void addSubTicks( sal_Int32 nDepth, + css::uno::Sequence< css::uno::Sequence< double > >& rParentTicks ) const; + double* getMajorTick( sal_Int32 nTick ) const; + double* getMinorTick( sal_Int32 nTick, sal_Int32 nDepth + , double fStartParentTick, double fNextParentTick ) const; + sal_Int32 getMaxTickCount( sal_Int32 nDepth ) const; + sal_Int32 getTickDepth() const; + + bool isVisible( double fValue ) const; + bool isWithinOuterBorder( double fScaledValue ) const; //all within the outer major tick marks + +private: //member + ExplicitScaleData m_rScale; + ExplicitIncrementData m_rIncrement; + css::uno::Reference< css::chart2::XScaling > m_xInverseScaling; + + //minimum and maximum of the visible range after scaling + double m_fScaledVisibleMin; + double m_fScaledVisibleMax; + + std::unique_ptr<double[]> + m_pfCurrentValues; + //major-tick positions that may lay outside the visible range but complete partly visible intervals at the borders + double m_fOuterMajorTickBorderMin; + double m_fOuterMajorTickBorderMax; + double m_fOuterMajorTickBorderMin_Scaled; + double m_fOuterMajorTickBorderMax_Scaled; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VAxisBase.cxx b/chart2/source/view/axes/VAxisBase.cxx new file mode 100644 index 000000000..ace362a9b --- /dev/null +++ b/chart2/source/view/axes/VAxisBase.cxx @@ -0,0 +1,244 @@ +/* -*- 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 "VAxisBase.hxx" +#include <ShapeFactory.hxx> +#include <ExplicitCategoriesProvider.hxx> +#include "Tickmarks.hxx" +#include <Axis.hxx> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/chart2/data/XTextualDataSequence.hpp> + +#include <osl/diagnose.h> + +#include <memory> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; + +VAxisBase::VAxisBase( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , const AxisProperties& rAxisProperties + , const uno::Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier ) + : VAxisOrGridBase( nDimensionIndex, nDimensionCount ) + , m_xNumberFormatsSupplier( xNumberFormatsSupplier ) + , m_aAxisProperties( rAxisProperties ) + , m_bUseTextLabels( false ) + , m_bReCreateAllTickInfos( true ) + , m_bRecordMaximumTextSize(false) + , m_nMaximumTextWidthSoFar(0) + , m_nMaximumTextHeightSoFar(0) +{ +} + +VAxisBase::~VAxisBase() +{ +} + +void VAxisBase::initAxisLabelProperties( const css::awt::Size& rFontReferenceSize + , const css::awt::Rectangle& rMaximumSpaceForLabels ) +{ + m_aAxisLabelProperties.m_aFontReferenceSize = rFontReferenceSize; + m_aAxisLabelProperties.m_aMaximumSpaceForLabels = rMaximumSpaceForLabels; + + if( !m_aAxisProperties.m_bDisplayLabels ) + return; + + if( m_aAxisProperties.m_nAxisType==AxisType::SERIES ) + { + if( m_aAxisProperties.m_xAxisTextProvider.is() ) + m_aTextLabels = m_aAxisProperties.m_xAxisTextProvider->getTextualData(); + + m_bUseTextLabels = true; + if( m_aTextLabels.getLength() == 1 ) + { + //don't show a single series name + m_aAxisProperties.m_bDisplayLabels = false; + return; + } + } + else if( m_aAxisProperties.m_nAxisType==AxisType::CATEGORY ) + { + if( m_aAxisProperties.m_pExplicitCategoriesProvider ) + m_aTextLabels = m_aAxisProperties.m_pExplicitCategoriesProvider->getSimpleCategories(); + + m_bUseTextLabels = true; + } + + m_aAxisLabelProperties.m_nNumberFormatKey = m_aAxisProperties.m_nNumberFormatKey; + m_aAxisLabelProperties.init(m_aAxisProperties.m_xAxisModel); + if( m_aAxisProperties.m_bComplexCategories && m_aAxisProperties.m_nAxisType == AxisType::CATEGORY ) + m_aAxisLabelProperties.m_eStaggering = AxisLabelStaggering::SideBySide; +} + +bool VAxisBase::isDateAxis() const +{ + return m_aScale.AxisType == AxisType::DATE; +} +bool VAxisBase::isComplexCategoryAxis() const +{ + return m_aAxisProperties.m_bComplexCategories && m_bUseTextLabels; +} + +void VAxisBase::recordMaximumTextSize( SvxShape& xShape, double fRotationAngleDegree ) +{ + if( m_bRecordMaximumTextSize ) + { + awt::Size aSize( ShapeFactory::getSizeAfterRotation( + xShape, fRotationAngleDegree ) ); + + m_nMaximumTextWidthSoFar = std::max( m_nMaximumTextWidthSoFar, aSize.Width ); + m_nMaximumTextHeightSoFar = std::max( m_nMaximumTextHeightSoFar, aSize.Height ); + } +} + +sal_Int32 VAxisBase::estimateMaximumAutoMainIncrementCount() +{ + return 10; +} + +void VAxisBase::setExtraLinePositionAtOtherAxis( double fCrossingAt ) +{ + m_aAxisProperties.m_pfExrtaLinePositionAtOtherAxis = fCrossingAt; +} + +sal_Int32 VAxisBase::getDimensionCount() const +{ + return m_nDimension; +} + +bool VAxisBase::isAnythingToDraw() +{ + if( !m_aAxisProperties.m_xAxisModel.is() ) + return false; + + OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"Axis is not proper initialized"); + if(!(m_xLogicTarget.is()&&m_xFinalTarget.is())) + return false; + + if( m_aAxisProperties.m_xAxisModel.is() ) + { + bool bShow = false; + m_aAxisProperties.m_xAxisModel->getPropertyValue( "Show" ) >>= bShow; + if( !bShow ) + return false; + } + return true; +} + +void VAxisBase::setExplicitScaleAndIncrement( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ) +{ + m_bReCreateAllTickInfos = true; + m_aScale = rScale; + m_aIncrement = rIncrement; +} + +void VAxisBase::createAllTickInfos( TickInfoArraysType& rAllTickInfos ) +{ + std::unique_ptr< TickFactory > apTickFactory( createTickFactory() ); + if( m_aScale.m_bShiftedCategoryPosition ) + apTickFactory->getAllTicksShifted( rAllTickInfos ); + else + apTickFactory->getAllTicks( rAllTickInfos ); +} + +bool VAxisBase::prepareShapeCreation() +{ + //returns true if all is ready for further shape creation and any shapes need to be created + if( !isAnythingToDraw() ) + return false; + + if( m_bReCreateAllTickInfos ) + { + //create all scaled tickmark values + removeTextShapesFromTicks(); + + createAllTickInfos(m_aAllTickInfos); + m_bReCreateAllTickInfos = false; + } + + if( m_xGroupShape_Shapes.is() ) + return true; + + //create named group shape + m_xGroupShape_Shapes = createGroupShape( m_xLogicTarget, m_nDimension==2 ? m_aCID : ""); + + if( m_aAxisProperties.m_bDisplayLabels ) + m_xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget, m_aCID ); + + return true; +} + +size_t VAxisBase::getIndexOfLongestLabel( const uno::Sequence<OUString>& rLabels ) +{ + sal_Int32 nRet = 0; + sal_Int32 nLength = 0; + sal_Int32 nN = 0; + for( nN=0; nN<rLabels.getLength(); nN++ ) + { + //todo: get real text width (without creating shape) instead of character count + if( rLabels[nN].getLength() > nLength ) + { + nLength = rLabels[nN].getLength(); + nRet = nN; + } + } + + assert(nRet >= 0); + return nRet; +} + +void VAxisBase::removeTextShapesFromTicks() +{ + if( !m_xTextTarget.is() ) + return; + + for (auto & tickInfos : m_aAllTickInfos) + { + for (auto & tickInfo : tickInfos) + { + if(tickInfo.xTextShape.is()) + { + m_xTextTarget->remove(tickInfo.xTextShape); + tickInfo.xTextShape = nullptr; + } + } + } +} + +void VAxisBase::updateUnscaledValuesAtTicks( TickIter& rIter ) +{ + Reference< XScaling > xInverseScaling; + if( m_aScale.Scaling.is() ) + xInverseScaling = m_aScale.Scaling->getInverseScaling(); + + for( TickInfo* pTickInfo = rIter.firstInfo() + ; pTickInfo; pTickInfo = rIter.nextInfo() ) + { + //xxxxx pTickInfo->updateUnscaledValue( xInverseScaling ); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VAxisBase.hxx b/chart2/source/view/axes/VAxisBase.hxx new file mode 100644 index 000000000..31badb749 --- /dev/null +++ b/chart2/source/view/axes/VAxisBase.hxx @@ -0,0 +1,102 @@ +/* -*- 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 . + */ +#pragma once + +#include "VAxisOrGridBase.hxx" +#include "VAxisProperties.hxx" +#include "Tickmarks.hxx" + +namespace com::sun::star::util { class XNumberFormatsSupplier; } + +namespace chart +{ + +class VAxisBase : public VAxisOrGridBase +{ +public: + VAxisBase( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , const AxisProperties& rAxisProperties + , const css::uno::Reference< css::util::XNumberFormatsSupplier >& xNumberFormatsSupplier ); + virtual ~VAxisBase() override; + + /** + * Return the number of dimensions the diagram has. 2 for x and y, and 3 + * for x, y, and z. + */ + sal_Int32 getDimensionCount() const; + + virtual void createMaximumLabels()=0; + virtual void createLabels()=0; + virtual void updatePositions()=0; + + virtual bool isAnythingToDraw(); + virtual void initAxisLabelProperties( + const css::awt::Size& rFontReferenceSize + , const css::awt::Rectangle& rMaximumSpaceForLabels ); + + virtual void setExplicitScaleAndIncrement( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ) override; + + virtual sal_Int32 estimateMaximumAutoMainIncrementCount(); + virtual void createAllTickInfos( TickInfoArraysType& rAllTickInfos ); + + void setExtraLinePositionAtOtherAxis( double fCrossingAt ); + +protected: //methods + static size_t getIndexOfLongestLabel( const css::uno::Sequence<OUString>& rLabels ); + void removeTextShapesFromTicks(); + void updateUnscaledValuesAtTicks( TickIter& rIter ); + + virtual bool prepareShapeCreation(); + void recordMaximumTextSize( SvxShape& xShape, double fRotationAngleDegree ); + + bool isDateAxis() const; + bool isComplexCategoryAxis() const; + +protected: //member + css::uno::Reference< css::util::XNumberFormatsSupplier > m_xNumberFormatsSupplier; + AxisProperties m_aAxisProperties; + AxisLabelProperties m_aAxisLabelProperties; + css::uno::Sequence< OUString > m_aTextLabels; + bool m_bUseTextLabels; + + rtl::Reference< SvxShapeGroupAnyD > m_xGroupShape_Shapes; + rtl::Reference< SvxShapeGroupAnyD > m_xTextTarget; + + /** + * This typically consists of 2 TickInfo vectors (i.e. the outer vector + * has 2 child vector elements) for normal axis. The first vector + * corresponds with the major ticks while the second corresponds with the + * minor ticks. + * + * It may have more than 2 TickInfo vectors for complex category axis + * which has multi-level axis labels. + */ + TickInfoArraysType m_aAllTickInfos; + bool m_bReCreateAllTickInfos; + + bool m_bRecordMaximumTextSize; + sal_Int32 m_nMaximumTextWidthSoFar; + sal_Int32 m_nMaximumTextHeightSoFar; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VAxisOrGridBase.cxx b/chart2/source/view/axes/VAxisOrGridBase.cxx new file mode 100644 index 000000000..290f3a368 --- /dev/null +++ b/chart2/source/view/axes/VAxisOrGridBase.cxx @@ -0,0 +1,70 @@ +/* -*- 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 "VAxisOrGridBase.hxx" +#include <CommonConverters.hxx> +#include "Tickmarks.hxx" + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +VAxisOrGridBase::VAxisOrGridBase( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount ) + : PlotterBase( nDimensionCount ) + , m_nDimensionIndex( nDimensionIndex ) + , m_eLeftWallPos(CuboidPlanePosition_Left) + , m_eBackWallPos(CuboidPlanePosition_Back) + , m_eBottomPos(CuboidPlanePosition_Bottom) +{ +} + +VAxisOrGridBase::~VAxisOrGridBase() +{ +} + +void VAxisOrGridBase::setExplicitScaleAndIncrement( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ) +{ + m_aScale = rScale; + m_aIncrement = rIncrement; +} + +void VAxisOrGridBase::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix ) +{ + m_aMatrixScreenToScene = HomogenMatrixToB3DHomMatrix(rMatrix); + PlotterBase::setTransformationSceneToScreen( rMatrix); +} + +void VAxisOrGridBase::set3DWallPositions( CuboidPlanePosition eLeftWallPos, CuboidPlanePosition eBackWallPos, CuboidPlanePosition eBottomPos ) +{ + m_eLeftWallPos = eLeftWallPos; + m_eBackWallPos = eBackWallPos; + m_eBottomPos = eBottomPos; +} + +TickFactory* VAxisOrGridBase::createTickFactory() +{ + return new TickFactory( m_aScale, m_aIncrement ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VAxisOrGridBase.hxx b/chart2/source/view/axes/VAxisOrGridBase.hxx new file mode 100644 index 000000000..1defc154a --- /dev/null +++ b/chart2/source/view/axes/VAxisOrGridBase.hxx @@ -0,0 +1,63 @@ +/* -*- 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 . + */ +#pragma once + +#include <PlotterBase.hxx> +#include <ThreeDHelper.hxx> +#include <chartview/ExplicitScaleValues.hxx> + +#include <basegfx/matrix/b3dhommatrix.hxx> + +namespace com::sun::star::drawing { struct HomogenMatrix; } + +namespace chart +{ + +class TickFactory; + +class VAxisOrGridBase : public PlotterBase +{ +public: + VAxisOrGridBase( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount ); + virtual ~VAxisOrGridBase() override; + + virtual void setTransformationSceneToScreen( const css::drawing::HomogenMatrix& rMatrix ) override; + /// @throws css::uno::RuntimeException + virtual void setExplicitScaleAndIncrement( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ); + void set3DWallPositions( CuboidPlanePosition eLeftWallPos, CuboidPlanePosition eBackWallPos, CuboidPlanePosition eBottomPos ); + + virtual TickFactory* createTickFactory(); + +protected: //member + ExplicitScaleData m_aScale; + ExplicitIncrementData m_aIncrement; + sal_Int32 m_nDimensionIndex; + + ::basegfx::B3DHomMatrix m_aMatrixScreenToScene; + + CuboidPlanePosition m_eLeftWallPos; + CuboidPlanePosition m_eBackWallPos; + CuboidPlanePosition m_eBottomPos; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VAxisProperties.cxx b/chart2/source/view/axes/VAxisProperties.cxx new file mode 100644 index 000000000..f8f177936 --- /dev/null +++ b/chart2/source/view/axes/VAxisProperties.cxx @@ -0,0 +1,392 @@ +/* -*- 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 "VAxisProperties.hxx" +#include <ViewDefines.hxx> +#include <Axis.hxx> +#include <AxisHelper.hxx> +#include <ChartModelHelper.hxx> +#include <ExplicitCategoriesProvider.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/chart/ChartAxisArrangeOrderType.hpp> +#include <com/sun/star/chart2/AxisType.hpp> + +#include <tools/diagnose_ex.h> +#include <rtl/math.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +namespace chart { + +AxisLabelAlignment::AxisLabelAlignment() : + mfLabelDirection(1.0), + mfInnerTickDirection(1.0), + meAlignment(LABEL_ALIGN_RIGHT_TOP) {} + +static sal_Int32 lcl_calcTickLengthForDepth(sal_Int32 nDepth,sal_Int32 nTickmarkStyle) +{ + sal_Int32 const nWidth = AXIS2D_TICKLENGTH; //@maybefuturetodo this length could be offered by the model + double fPercent = 1.0; + switch(nDepth) + { + case 0: + fPercent = 1.0; + break; + case 1: + fPercent = 0.75;//percentage like in the old chart + break; + case 2: + fPercent = 0.5; + break; + default: + fPercent = 0.3; + break; + } + if(nTickmarkStyle==3)//inner and outer tickmarks + fPercent*=2.0; + return static_cast<sal_Int32>(nWidth*fPercent); +} + +static double lcl_getTickOffset(sal_Int32 nLength,sal_Int32 nTickmarkStyle) +{ + double fPercent = 0.0; //0<=fPercent<=1 + //0.0: completely inner + //1.0: completely outer + //0.5: half and half + + /* + nTickmarkStyle: + 1: inner tickmarks + 2: outer tickmarks + 3: inner and outer tickmarks + */ + switch(nTickmarkStyle) + { + case 1: + fPercent = 0.0; + break; + case 2: + fPercent = 1.0; + break; + default: + fPercent = 0.5; + break; + } + return fPercent*nLength; +} + +TickmarkProperties AxisProperties::makeTickmarkProperties( + sal_Int32 nDepth ) const +{ + /* + nTickmarkStyle: + 1: inner tickmarks + 2: outer tickmarks + 3: inner and outer tickmarks + */ + sal_Int32 nTickmarkStyle = 1; + if(nDepth==0) + { + nTickmarkStyle = m_nMajorTickmarks; + if(!nTickmarkStyle) + { + //create major tickmarks as if they were minor tickmarks + nDepth = 1; + nTickmarkStyle = m_nMinorTickmarks; + } + } + else if( nDepth==1) + { + nTickmarkStyle = m_nMinorTickmarks; + } + + if (maLabelAlignment.mfInnerTickDirection == 0.0) + { + if( nTickmarkStyle != 0 ) + nTickmarkStyle = 3; //inner and outer tickmarks + } + + TickmarkProperties aTickmarkProperties; + aTickmarkProperties.Length = lcl_calcTickLengthForDepth(nDepth,nTickmarkStyle); + aTickmarkProperties.RelativePos = static_cast<sal_Int32>(lcl_getTickOffset(aTickmarkProperties.Length,nTickmarkStyle)); + aTickmarkProperties.aLineProperties = makeLinePropertiesForDepth(); + return aTickmarkProperties; +} + +TickmarkProperties AxisProperties::makeTickmarkPropertiesForComplexCategories( + sal_Int32 nTickLength, sal_Int32 nTickStartDistanceToAxis ) const +{ + sal_Int32 nTickmarkStyle = (maLabelAlignment.mfLabelDirection == maLabelAlignment.mfInnerTickDirection) ? 2/*outside*/ : 1/*inside*/; + + TickmarkProperties aTickmarkProperties; + aTickmarkProperties.Length = nTickLength;// + nTextLevel*( lcl_calcTickLengthForDepth(0,nTickmarkStyle) ); + aTickmarkProperties.RelativePos = static_cast<sal_Int32>(lcl_getTickOffset(aTickmarkProperties.Length+nTickStartDistanceToAxis,nTickmarkStyle)); + aTickmarkProperties.aLineProperties = makeLinePropertiesForDepth(); + return aTickmarkProperties; +} + +TickmarkProperties AxisProperties::getBiggestTickmarkProperties() +{ + TickmarkProperties aTickmarkProperties; + sal_Int32 nTickmarkStyle = 3;//inner and outer tickmarks + aTickmarkProperties.Length = lcl_calcTickLengthForDepth( 0/*nDepth*/,nTickmarkStyle ); + aTickmarkProperties.RelativePos = static_cast<sal_Int32>( lcl_getTickOffset( aTickmarkProperties.Length, nTickmarkStyle ) ); + return aTickmarkProperties; +} + +AxisProperties::AxisProperties( const rtl::Reference< Axis >& xAxisModel + , ExplicitCategoriesProvider* pExplicitCategoriesProvider ) + : m_xAxisModel(xAxisModel) + , m_nDimensionIndex(0) + , m_bIsMainAxis(true) + , m_bSwapXAndY(false) + , m_eCrossoverType( css::chart::ChartAxisPosition_ZERO ) + , m_eLabelPos( css::chart::ChartAxisLabelPosition_NEAR_AXIS ) + , m_eTickmarkPos( css::chart::ChartAxisMarkPosition_AT_LABELS_AND_AXIS ) + , m_bCrossingAxisHasReverseDirection(false) + , m_bCrossingAxisIsCategoryAxes(false) + , m_bDisplayLabels( true ) + , m_bTryStaggeringFirst( false ) + , m_nNumberFormatKey(0) + , m_nMajorTickmarks(1) + , m_nMinorTickmarks(1) + , m_nAxisType(AxisType::REALNUMBER) + , m_bComplexCategories(false) + , m_pExplicitCategoriesProvider(pExplicitCategoriesProvider) + , m_bLimitSpaceForLabels(false) +{ +} + +static LabelAlignment lcl_getLabelAlignmentForZAxis( const AxisProperties& rAxisProperties ) +{ + LabelAlignment aRet( LABEL_ALIGN_RIGHT ); + if (rAxisProperties.maLabelAlignment.mfLabelDirection < 0) + aRet = LABEL_ALIGN_LEFT; + return aRet; +} + +static LabelAlignment lcl_getLabelAlignmentForYAxis( const AxisProperties& rAxisProperties ) +{ + LabelAlignment aRet( LABEL_ALIGN_RIGHT ); + if (rAxisProperties.maLabelAlignment.mfLabelDirection < 0) + aRet = LABEL_ALIGN_LEFT; + return aRet; +} + +static LabelAlignment lcl_getLabelAlignmentForXAxis( const AxisProperties& rAxisProperties ) +{ + LabelAlignment aRet( LABEL_ALIGN_BOTTOM ); + if (rAxisProperties.maLabelAlignment.mfLabelDirection < 0) + aRet = LABEL_ALIGN_TOP; + return aRet; +} + +void AxisProperties::initAxisPositioning( const uno::Reference< beans::XPropertySet >& xAxisProp ) +{ + if( !xAxisProp.is() ) + return; + try + { + if( AxisHelper::isAxisPositioningEnabled() ) + { + xAxisProp->getPropertyValue("CrossoverPosition") >>= m_eCrossoverType; + if( m_eCrossoverType == css::chart::ChartAxisPosition_VALUE ) + { + double fValue = 0.0; + xAxisProp->getPropertyValue("CrossoverValue") >>= fValue; + + if( m_bCrossingAxisIsCategoryAxes ) + fValue = ::rtl::math::round(fValue); + m_pfMainLinePositionAtOtherAxis = fValue; + } + else if( m_eCrossoverType == css::chart::ChartAxisPosition_ZERO ) + m_pfMainLinePositionAtOtherAxis = 0.0; + + xAxisProp->getPropertyValue("LabelPosition") >>= m_eLabelPos; + xAxisProp->getPropertyValue("MarkPosition") >>= m_eTickmarkPos; + } + else + { + m_eCrossoverType = css::chart::ChartAxisPosition_START; + if( m_bIsMainAxis == m_bCrossingAxisHasReverseDirection ) + m_eCrossoverType = css::chart::ChartAxisPosition_END; + m_eLabelPos = css::chart::ChartAxisLabelPosition_NEAR_AXIS; + m_eTickmarkPos = css::chart::ChartAxisMarkPosition_AT_LABELS; + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +void AxisProperties::init( bool bCartesian ) +{ + if( !m_xAxisModel.is() ) + return; + + if( m_nDimensionIndex<2 ) + initAxisPositioning( m_xAxisModel ); + + ScaleData aScaleData = m_xAxisModel->getScaleData(); + if( m_nDimensionIndex==0 ) + AxisHelper::checkDateAxis( aScaleData, m_pExplicitCategoriesProvider, bCartesian ); + m_nAxisType = aScaleData.AxisType; + + if( bCartesian ) + { + if( m_nDimensionIndex == 0 && m_nAxisType == AxisType::CATEGORY + && m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->hasComplexCategories() ) + m_bComplexCategories = true; + + if( m_eCrossoverType == css::chart::ChartAxisPosition_END ) + maLabelAlignment.mfInnerTickDirection = m_bCrossingAxisHasReverseDirection ? 1.0 : -1.0; + else + maLabelAlignment.mfInnerTickDirection = m_bCrossingAxisHasReverseDirection ? -1.0 : 1.0; + + if( m_eLabelPos == css::chart::ChartAxisLabelPosition_NEAR_AXIS ) + maLabelAlignment.mfLabelDirection = maLabelAlignment.mfInnerTickDirection; + else if( m_eLabelPos == css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE ) + maLabelAlignment.mfLabelDirection = -maLabelAlignment.mfInnerTickDirection; + else if( m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START ) + maLabelAlignment.mfLabelDirection = m_bCrossingAxisHasReverseDirection ? -1 : 1; + else if( m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END ) + maLabelAlignment.mfLabelDirection = m_bCrossingAxisHasReverseDirection ? 1 : -1; + + if( m_nDimensionIndex==2 ) + maLabelAlignment.meAlignment = lcl_getLabelAlignmentForZAxis(*this); + else + { + bool bIsYAxisPosition = (m_nDimensionIndex==1 && !m_bSwapXAndY) + || (m_nDimensionIndex==0 && m_bSwapXAndY); + if( bIsYAxisPosition ) + { + maLabelAlignment.mfLabelDirection *= -1.0; + maLabelAlignment.mfInnerTickDirection *= -1.0; + + maLabelAlignment.meAlignment = lcl_getLabelAlignmentForYAxis(*this); + } + else + maLabelAlignment.meAlignment = lcl_getLabelAlignmentForXAxis(*this); + } + } + + try + { + //init LineProperties + m_aLineProperties.initFromPropertySet( m_xAxisModel ); + + //init display labels + m_xAxisModel->getPropertyValue( "DisplayLabels" ) >>= m_bDisplayLabels; + + // Init layout strategy hint for axis labels. + // Compatibility option: starting from LibreOffice 5.1 the rotated + // layout is preferred to staggering for axis labels. + m_xAxisModel->getPropertyValue( "TryStaggeringFirst" ) >>= m_bTryStaggeringFirst; + + //init TickmarkProperties + m_xAxisModel->getPropertyValue( "MajorTickmarks" ) >>= m_nMajorTickmarks; + m_xAxisModel->getPropertyValue( "MinorTickmarks" ) >>= m_nMinorTickmarks; + + sal_Int32 nMaxDepth = 0; + if(m_nMinorTickmarks!=0) + nMaxDepth=2; + else if(m_nMajorTickmarks!=0) + nMaxDepth=1; + + m_aTickmarkPropertiesList.clear(); + for( sal_Int32 nDepth=0; nDepth<nMaxDepth; nDepth++ ) + { + TickmarkProperties aTickmarkProperties = makeTickmarkProperties( nDepth ); + m_aTickmarkPropertiesList.push_back( aTickmarkProperties ); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +AxisLabelProperties::AxisLabelProperties() + : m_aFontReferenceSize( ChartModelHelper::getDefaultPageSize() ) + , m_aMaximumSpaceForLabels( 0 , 0, m_aFontReferenceSize.Width, m_aFontReferenceSize.Height ) + , m_nNumberFormatKey(0) + , m_eStaggering( AxisLabelStaggering::SideBySide ) + , m_bLineBreakAllowed( false ) + , m_bOverlapAllowed( false ) + , m_bStackCharacters( false ) + , m_fRotationAngleDegree( 0.0 ) + , m_nRhythm( 1 ) +{ + +} + +void AxisLabelProperties::init( const rtl::Reference< Axis >& xAxisModel ) +{ + if(!xAxisModel.is()) + return; + + try + { + xAxisModel->getPropertyValue( "TextBreak" ) >>= m_bLineBreakAllowed; + xAxisModel->getPropertyValue( "TextOverlap" ) >>= m_bOverlapAllowed; + xAxisModel->getPropertyValue( "StackCharacters" ) >>= m_bStackCharacters; + xAxisModel->getPropertyValue( "TextRotation" ) >>= m_fRotationAngleDegree; + + css::chart::ChartAxisArrangeOrderType eArrangeOrder; + xAxisModel->getPropertyValue( "ArrangeOrder" ) >>= eArrangeOrder; + switch(eArrangeOrder) + { + case css::chart::ChartAxisArrangeOrderType_SIDE_BY_SIDE: + m_eStaggering = AxisLabelStaggering::SideBySide; + break; + case css::chart::ChartAxisArrangeOrderType_STAGGER_EVEN: + m_eStaggering = AxisLabelStaggering::StaggerEven; + break; + case css::chart::ChartAxisArrangeOrderType_STAGGER_ODD: + m_eStaggering = AxisLabelStaggering::StaggerOdd; + break; + default: + m_eStaggering = AxisLabelStaggering::StaggerAuto; + break; + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +bool AxisLabelProperties::isStaggered() const +{ + return ( m_eStaggering == AxisLabelStaggering::StaggerOdd || m_eStaggering == AxisLabelStaggering::StaggerEven ); +} + +void AxisLabelProperties::autoRotate45() +{ + m_fRotationAngleDegree = 45; + m_bLineBreakAllowed = false; + m_eStaggering = AxisLabelStaggering::SideBySide; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VAxisProperties.hxx b/chart2/source/view/axes/VAxisProperties.hxx new file mode 100644 index 000000000..4370ccbb6 --- /dev/null +++ b/chart2/source/view/axes/VAxisProperties.hxx @@ -0,0 +1,164 @@ +/* -*- 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 . + */ +#pragma once + +#include "TickmarkProperties.hxx" +#include <LabelAlignment.hxx> + +#include <com/sun/star/chart/ChartAxisLabelPosition.hpp> +#include <com/sun/star/chart/ChartAxisMarkPosition.hpp> +#include <com/sun/star/chart/ChartAxisPosition.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <rtl/ref.hxx> + +#include <vector> +#include <optional> + +namespace chart { class ExplicitCategoriesProvider; } +namespace com::sun::star::beans { class XPropertySet; } +namespace com::sun::star::chart2 { class XAxis; } +namespace com::sun::star::chart2::data { class XTextualDataSequence; } + +namespace chart +{ +class Axis; + +//These properties describe how a couple of labels are arranged one to another. +//The couple can contain all labels for all tickmark depth or just the labels for one single depth or +//the labels from a coherent range of tick depths (e.g. the major and first minor tickmarks should be handled together). +//... only allow side by side for different tick depth +enum class AxisLabelStaggering +{ + SideBySide + , StaggerEven + , StaggerOdd + , StaggerAuto +}; + +struct AxisLabelProperties final +{ + AxisLabelProperties(); + + css::awt::Size m_aFontReferenceSize;//reference size to calculate the font height + css::awt::Rectangle m_aMaximumSpaceForLabels;//Labels need to be clipped in order to fit into this rectangle + + sal_Int32 m_nNumberFormatKey; + + AxisLabelStaggering m_eStaggering; + + bool m_bLineBreakAllowed; + bool m_bOverlapAllowed; + + bool m_bStackCharacters; + double m_fRotationAngleDegree; + + sal_Int32 m_nRhythm; //show only each nth label with n==nRhythm + + //methods: + void init( const rtl::Reference< ::chart::Axis >& xAxisModel ); + + bool isStaggered() const; + + void autoRotate45(); +}; + +struct AxisLabelAlignment +{ + double mfLabelDirection; /// which direction the labels are to be drawn. + double mfInnerTickDirection; /// which direction the inner tickmarks are to be drawn. + + LabelAlignment meAlignment; + + AxisLabelAlignment(); +}; + +struct AxisProperties final +{ + rtl::Reference<::chart::Axis> m_xAxisModel; + + sal_Int32 m_nDimensionIndex; + bool m_bIsMainAxis;//not secondary axis + bool m_bSwapXAndY; + + css::chart::ChartAxisPosition m_eCrossoverType; + css::chart::ChartAxisLabelPosition m_eLabelPos; + css::chart::ChartAxisMarkPosition m_eTickmarkPos; + + std::optional<double> m_pfMainLinePositionAtOtherAxis; + std::optional<double> m_pfExrtaLinePositionAtOtherAxis; + + bool m_bCrossingAxisHasReverseDirection; + bool m_bCrossingAxisIsCategoryAxes; + + AxisLabelAlignment maLabelAlignment; + + bool m_bDisplayLabels; + + // Compatibility option: starting from LibreOffice 5.1 the rotated + // layout is preferred to staggering for axis labels. + // So the default value of this flag for new documents is `false`. + bool m_bTryStaggeringFirst; + + sal_Int32 m_nNumberFormatKey; + + /* + 0: no tickmarks 1: inner tickmarks + 2: outer tickmarks 3: inner and outer tickmarks + */ + sal_Int32 m_nMajorTickmarks; + sal_Int32 m_nMinorTickmarks; + std::vector<TickmarkProperties> m_aTickmarkPropertiesList; + + VLineProperties m_aLineProperties; + + //for category axes -> + sal_Int32 m_nAxisType;//REALNUMBER, CATEGORY etc. type css::chart2::AxisType + bool m_bComplexCategories; + ExplicitCategoriesProvider* m_pExplicitCategoriesProvider;/*no ownership here*/ + css::uno::Reference<css::chart2::data::XTextualDataSequence> m_xAxisTextProvider; //for categories or series names + //<- category axes + + bool m_bLimitSpaceForLabels; + + //methods: + + AxisProperties( const rtl::Reference< ::chart::Axis >& xAxisModel + , ExplicitCategoriesProvider* pExplicitCategoriesProvider ); + + void init(bool bCartesian=false);//init from model data (m_xAxisModel) + + void initAxisPositioning( const css::uno::Reference< css::beans::XPropertySet >& xAxisProp ); + + static TickmarkProperties getBiggestTickmarkProperties(); + TickmarkProperties makeTickmarkPropertiesForComplexCategories( sal_Int32 nTickLength, sal_Int32 nTickStartDistanceToAxis ) const; + +private: + AxisProperties() = delete; + + TickmarkProperties makeTickmarkProperties( sal_Int32 nDepth ) const; + //@todo get this from somewhere; maybe for each subincrement + //so far the model does not offer different settings for each tick depth + const VLineProperties& makeLinePropertiesForDepth() const { return m_aLineProperties; } +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCartesianAxis.cxx b/chart2/source/view/axes/VCartesianAxis.cxx new file mode 100644 index 000000000..0ea37f9a7 --- /dev/null +++ b/chart2/source/view/axes/VCartesianAxis.cxx @@ -0,0 +1,1971 @@ +/* -*- 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 <tools/diagnose_ex.h> +#include <tools/color.hxx> +#include <svx/unoshape.hxx> +#include <svx/unoshtxt.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, + const OUString& 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.getLength() ) + return; + + const sal_Int32 nAvgCharWidth = rShape2DText.getSize().Width / rLabel.getLength(); + + 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 const OUStringLiteral sDots = u"..."; + const sal_Int32 nCharsToRemove = ( nTextSize - nMaxLabelsSize ) / nAvgCharWidth + 1; + sal_Int32 nNewLen = rLabel.getLength() - nCharsToRemove - sDots.getLength(); + // Prevent from showing only dots + if (nNewLen < 0) + nNewLen = ( rLabel.getLength() >= sDots.getLength() ) ? sDots.getLength() : rLabel.getLength(); + + bool bCrop = nCharsToRemove > 0; + if( !bCrop ) + return; + + OUString aNewLabel = rLabel.copy( 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::createLabels() +{ + 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 ); + //'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 ); +} + +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( "Transformation", 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, "MarkHandles" ); + } + //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(); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCartesianAxis.hxx b/chart2/source/view/axes/VCartesianAxis.hxx new file mode 100644 index 000000000..94e9b2ab9 --- /dev/null +++ b/chart2/source/view/axes/VCartesianAxis.hxx @@ -0,0 +1,160 @@ +/* -*- 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 . + */ +#pragma once + +#include "VAxisBase.hxx" +#include <basegfx/vector/b2dvector.hxx> + +namespace chart +{ + +class VCartesianAxis : public VAxisBase +{ + // public methods +public: + VCartesianAxis( const AxisProperties& rAxisProperties + , const css::uno::Reference< css::util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , PlottingPositionHelper* pPosHelper = nullptr //takes ownership + ); + + virtual ~VCartesianAxis() override; + + virtual void createMaximumLabels() override; + virtual void createLabels() override; + virtual void updatePositions() override; + + virtual void createShapes() override; + + virtual sal_Int32 estimateMaximumAutoMainIncrementCount() override; + virtual void createAllTickInfos( TickInfoArraysType& rAllTickInfos ) override; + void createAllTickInfosFromComplexCategories( TickInfoArraysType& rAllTickInfos, bool bShiftedPosition ); + + TickIter* createLabelTickIterator( sal_Int32 nTextLevel ); + TickIter* createMaximumLabelTickIterator( sal_Int32 nTextLevel ); + sal_Int32 getTextLevelCount() const; + + virtual TickFactory* createTickFactory() override; + + /** + * Get the value at which the other axis crosses. + */ + double getAxisIntersectionValue() const; + + /** + * Get the value at which label line crosses the other axis. + */ + double getLabelLineIntersectionValue() const; + + /** + * Get the value at which extra line crosses the other axis. + * + * @return a NaN if the line doesn't cross the other axis, a non-NaN value + * otherwise. + */ + double getExtraLineIntersectionValue() const; + + void get2DAxisMainLine( + basegfx::B2DVector& rStart, basegfx::B2DVector& rEnd, AxisLabelAlignment& rLabelAlignment, + double fCrossesOtherAxis ) const; + + //Layout interface for cartesian axes: + + //the returned value describes the minimum size that is necessary + //for the text labels in the direction orthogonal to the axis + //(for an y-axis a width is returned; in case of an x-axis the value describes a height) + //the return value is measured in screen dimension + //As an example the MinimumOrthogonalSize of an x-axis equals the + //Font Height if the label properties allow for labels parallel to the axis. +// sal_Int32 calculateMinimumOrthogonalSize( /*... parallel...*/ ); + //Minimum->Preferred + + //returns true if the MinimumOrthogonalSize can be calculated + //with the creation of at most one text shape + //(this is e.g. true if the parameters allow for labels parallel to the axis.) +// sal_bool canQuicklyCalculateMinimumOrthogonalSize(); + + struct ScreenPosAndLogicPos + { + double fLogicX; + double fLogicY; + double fLogicZ; + + ::basegfx::B2DVector aScreenPos; + }; + +private: //methods + /** + * Go through all tick label positions and decide which labels to display + * based on the text shape geometry, overlap setting, tick interval, + * auto-stagger setting etc. + * + * When the auto-stagger setting is on, try to avoid overlaps by + * staggering labels or set the labels at an angle. This method may + * change the axis label properties especially when the auto staggering is + * performed. But the screen label positions will not be shifted in this + * method; it will be done in the doStaggeringOfLabels method. + * + * @return true if the text shapes have been successfully created, + * otherwise false. Returning false means the AxisLabelProperties + * have changed during the call, and the caller needs to call this + * method once again to get the text shapes created. + */ + bool createTextShapes( + const rtl::Reference< SvxShapeGroupAnyD >& xTarget, + TickIter& rTickIter, AxisLabelProperties& rAxisLabelProperties, + TickFactory2D const * pTickFactory, sal_Int32 nScreenDistanceBetweenTicks ); + + /** + * Variant of createTextShapes where none of auto-staggering and + * link-breaking are allowed in case of overlaps. Overlaps of text shapes + * are to be resolved only by adjusting the label tick interval. + */ + bool createTextShapesSimple( + const rtl::Reference< SvxShapeGroupAnyD >& xTarget, + TickIter& rTickIter, AxisLabelProperties& rAxisLabelProperties, + TickFactory2D const * pTickFactory ); + + void createTickMarkLineShapes( TickInfoArrayType& rTickInfos, const TickmarkProperties& rTickmarkProperties, TickFactory2D const & rTickFactory2D, bool bOnlyAtLabels ); + + TickFactory2D* createTickFactory2D(); + void hideIdenticalScreenValues( TickInfoArraysType& rTickInfos ) const; + + /** + * Shift the screen positions of the tick labels according to the stagger + * settings. Final stagger setting is decided during the createTextShapes + * call, but this method does the physical shifting of the label + * positions based on the final stagger setting. + */ + void doStaggeringOfLabels( const AxisLabelProperties& rAxisLabelProperties + , TickFactory2D const * pTickFactory2D ); + + /** + * @return true if we can break a single line label text into multiple + * lines for better fitting, otherwise false. + */ + bool isBreakOfLabelsAllowed( const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis ) const; + + ::basegfx::B2DVector getScreenPosition( double fLogicX, double fLogicY, double fLogicZ ) const; + ScreenPosAndLogicPos getScreenPosAndLogicPos( double fLogicX, double fLogicY, double fLogicZ ) const; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCartesianCoordinateSystem.cxx b/chart2/source/view/axes/VCartesianCoordinateSystem.cxx new file mode 100644 index 000000000..1257ff6f4 --- /dev/null +++ b/chart2/source/view/axes/VCartesianCoordinateSystem.cxx @@ -0,0 +1,216 @@ +/* -*- 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 "VCartesianCoordinateSystem.hxx" +#include "VCartesianGrid.hxx" +#include "VCartesianAxis.hxx" +#include <BaseCoordinateSystem.hxx> +#include <AxisIndexDefines.hxx> +#include <Axis.hxx> +#include <AxisHelper.hxx> +#include <cppuhelper/implbase.hxx> +#include <ChartModel.hxx> +#include <com/sun/star/chart2/data/XTextualDataSequence.hpp> +#include <com/sun/star/chart2/AxisType.hpp> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; + +namespace { + +class TextualDataProvider : public ::cppu::WeakImplHelper< + css::chart2::data::XTextualDataSequence + > +{ +public: + explicit TextualDataProvider( const uno::Sequence< OUString >& rTextSequence ) + : m_aTextSequence( rTextSequence ) + { + } + + //XTextualDataSequence + virtual uno::Sequence< OUString > SAL_CALL getTextualData() override + { + return m_aTextSequence; + } + +private: //member + uno::Sequence< OUString > m_aTextSequence; +}; + +} + +VCartesianCoordinateSystem::VCartesianCoordinateSystem( const rtl::Reference< BaseCoordinateSystem >& xCooSys ) + : VCoordinateSystem(xCooSys) +{ +} + +VCartesianCoordinateSystem::~VCartesianCoordinateSystem() +{ +} + +void VCartesianCoordinateSystem::createGridShapes() +{ + if(!m_xLogicTargetForGrids.is() || !m_xFinalTarget.is() ) + return; + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + for( sal_Int32 nDimensionIndex=0; nDimensionIndex<3; nDimensionIndex++) + { + sal_Int32 nAxisIndex = MAIN_AXIS_INDEX; + rtl::Reference< Axis > xAxis = AxisHelper::getAxis( nDimensionIndex, nAxisIndex, m_xCooSysModel ); + if(!xAxis.is() || !AxisHelper::shouldAxisBeDisplayed( xAxis, m_xCooSysModel )) + continue; + + VCartesianGrid aGrid(nDimensionIndex,nDimensionCount, getGridListFromAxis( xAxis )); + aGrid.setExplicitScaleAndIncrement( getExplicitScale(nDimensionIndex,nAxisIndex) + , getExplicitIncrement(nDimensionIndex,nAxisIndex) ); + aGrid.set3DWallPositions( m_eLeftWallPos, m_eBackWallPos, m_eBottomPos ); + + aGrid.initPlotter(m_xLogicTargetForGrids,m_xFinalTarget + , createCIDForGrid( nDimensionIndex,nAxisIndex ) ); + if(nDimensionCount==2) + aGrid.setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + aGrid.setScales( getExplicitScales(nDimensionIndex,nAxisIndex), bSwapXAndY ); + aGrid.createShapes(); + } +} + +void VCartesianCoordinateSystem::createVAxisList( + const rtl::Reference<::chart::ChartModel> & xChartDoc + , const awt::Size& rFontReferenceSize + , const awt::Rectangle& rMaximumSpaceForLabels + , bool bLimitSpaceForLabels + ) +{ + // note: using xChartDoc itself as XNumberFormatsSupplier would cause + // a leak from VCartesianAxis due to cyclic reference + uno::Reference<util::XNumberFormatsSupplier> const xNumberFormatsSupplier( + dynamic_cast<ChartModel&>(*xChartDoc).getNumberFormatsSupplier()); + + m_aAxisMap.clear(); + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + if(nDimensionCount<=0) + return; + + sal_Int32 nDimensionIndex = 0; + + // dimension index -> x, y or z axis. + for( nDimensionIndex = 0; nDimensionIndex < nDimensionCount; nDimensionIndex++ ) + { + // axis index -> primary or secondary axis. + sal_Int32 nMaxAxisIndex = m_xCooSysModel->getMaximumAxisIndexByDimension(nDimensionIndex); + for( sal_Int32 nAxisIndex = 0; nAxisIndex <= nMaxAxisIndex; nAxisIndex++ ) + { + rtl::Reference< Axis > xAxis = getAxisByDimension(nDimensionIndex,nAxisIndex); + if(!xAxis.is() || !AxisHelper::shouldAxisBeDisplayed( xAxis, m_xCooSysModel )) + continue; + + AxisProperties aAxisProperties(xAxis,getExplicitCategoriesProvider()); + aAxisProperties.m_nDimensionIndex = nDimensionIndex; + aAxisProperties.m_bSwapXAndY = bSwapXAndY; + aAxisProperties.m_bIsMainAxis = (nAxisIndex==0); + aAxisProperties.m_bLimitSpaceForLabels = bLimitSpaceForLabels; + rtl::Reference< Axis > xCrossingMainAxis = AxisHelper::getCrossingMainAxis( xAxis, m_xCooSysModel ); + if( xCrossingMainAxis.is() ) + { + ScaleData aCrossingScale( xCrossingMainAxis->getScaleData() ); + aAxisProperties.m_bCrossingAxisHasReverseDirection = (aCrossingScale.Orientation==AxisOrientation_REVERSE); + + if( aCrossingScale.AxisType == AxisType::CATEGORY ) + aAxisProperties.m_bCrossingAxisIsCategoryAxes = true; + } + + if( nDimensionIndex == 2 ) + { + aAxisProperties.m_xAxisTextProvider = new TextualDataProvider( m_aSeriesNamesForZAxis ); + + //for the z axis copy the positioning properties from the x axis (or from the y axis for swapped coordinate systems) + rtl::Reference< Axis > xSisterAxis = AxisHelper::getCrossingMainAxis( xCrossingMainAxis, m_xCooSysModel ); + aAxisProperties.initAxisPositioning( xSisterAxis ); + } + aAxisProperties.init(true); + if(aAxisProperties.m_bDisplayLabels) + aAxisProperties.m_nNumberFormatKey = getNumberFormatKeyForAxis(xAxis, xChartDoc); + + auto apVAxis = std::make_shared<VCartesianAxis>(aAxisProperties,xNumberFormatsSupplier,nDimensionIndex,nDimensionCount); + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + m_aAxisMap[aFullAxisIndex] = apVAxis; + apVAxis->set3DWallPositions( m_eLeftWallPos, m_eBackWallPos, m_eBottomPos ); + + apVAxis->initAxisLabelProperties(rFontReferenceSize,rMaximumSpaceForLabels); + } + } +} + +void VCartesianCoordinateSystem::initVAxisInList() +{ + if(!m_xLogicTargetForAxes.is() || !m_xFinalTarget.is() || !m_xCooSysModel.is() ) + return; + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + for (auto const& [nIndexPair, pVAxis] : m_aAxisMap) + { + if (pVAxis) + { + auto [nDimensionIndex, nAxisIndex] = nIndexPair; + pVAxis->setExplicitScaleAndIncrement( getExplicitScale( nDimensionIndex, nAxisIndex ), getExplicitIncrement( nDimensionIndex, nAxisIndex ) ); + pVAxis->initPlotter(m_xLogicTargetForAxes, m_xFinalTarget, createCIDForAxis( nDimensionIndex, nAxisIndex ) ); + if(nDimensionCount==2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + pVAxis->setScales( getExplicitScales(nDimensionIndex,nAxisIndex), bSwapXAndY ); + } + } +} + +void VCartesianCoordinateSystem::updateScalesAndIncrementsOnAxes() +{ + if(!m_xLogicTargetForAxes.is() || !m_xFinalTarget.is() || !m_xCooSysModel.is() ) + return; + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + for (auto const& [nIndexPair, pVAxis] : m_aAxisMap) + { + if (pVAxis) + { + auto [nDimensionIndex, nAxisIndex] = nIndexPair; + + pVAxis->setExplicitScaleAndIncrement( getExplicitScale( nDimensionIndex, nAxisIndex ), getExplicitIncrement( nDimensionIndex, nAxisIndex ) ); + if(nDimensionCount==2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + pVAxis->setScales( getExplicitScales(nDimensionIndex,nAxisIndex), bSwapXAndY ); + } + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCartesianCoordinateSystem.hxx b/chart2/source/view/axes/VCartesianCoordinateSystem.hxx new file mode 100644 index 000000000..e9d684821 --- /dev/null +++ b/chart2/source/view/axes/VCartesianCoordinateSystem.hxx @@ -0,0 +1,47 @@ +/* -*- 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 . + */ +#pragma once + +#include <VCoordinateSystem.hxx> + +namespace chart +{ + +class VCartesianCoordinateSystem : public VCoordinateSystem +{ +public: + VCartesianCoordinateSystem() = delete; + explicit VCartesianCoordinateSystem( const rtl::Reference< ::chart::BaseCoordinateSystem >& xCooSys ); + virtual ~VCartesianCoordinateSystem() override; + + virtual void createVAxisList( + const rtl::Reference<::chart::ChartModel> &ChartDoc + , const css::awt::Size& rFontReferenceSize + , const css::awt::Rectangle& rMaximumSpaceForLabels + , bool bLimitSpaceForLabels ) override; + + virtual void initVAxisInList() override; + virtual void updateScalesAndIncrementsOnAxes() override; + + virtual void createGridShapes() override; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCartesianGrid.cxx b/chart2/source/view/axes/VCartesianGrid.cxx new file mode 100644 index 000000000..a5c44c22b --- /dev/null +++ b/chart2/source/view/axes/VCartesianGrid.cxx @@ -0,0 +1,309 @@ +/* -*- 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 "VCartesianGrid.hxx" +#include "Tickmarks.hxx" +#include <PlottingPositionHelper.hxx> +#include <ShapeFactory.hxx> +#include <ObjectIdentifier.hxx> +#include <CommonConverters.hxx> +#include <AxisHelper.hxx> +#include <VLineProperties.hxx> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> + +#include <memory> +#include <vector> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; + +namespace { + +struct GridLinePoints +{ + Sequence< double > P0; + Sequence< double > P1; + Sequence< double > P2; + + GridLinePoints( const PlottingPositionHelper* pPosHelper, sal_Int32 nDimensionIndex + , CuboidPlanePosition eLeftWallPos=CuboidPlanePosition_Left + , CuboidPlanePosition eBackWallPos=CuboidPlanePosition_Back + , CuboidPlanePosition eBottomPos=CuboidPlanePosition_Bottom ); + void update( double fScaledTickValue ); + + sal_Int32 m_nDimensionIndex; +}; + +} + +GridLinePoints::GridLinePoints( const PlottingPositionHelper* pPosHelper, sal_Int32 nDimensionIndex + , CuboidPlanePosition eLeftWallPos + , CuboidPlanePosition eBackWallPos + , CuboidPlanePosition eBottomPos ) + : m_nDimensionIndex(nDimensionIndex) +{ + double MinX = pPosHelper->getLogicMinX(); + double MinY = pPosHelper->getLogicMinY(); + double MinZ = pPosHelper->getLogicMinZ(); + double MaxX = pPosHelper->getLogicMaxX(); + double MaxY = pPosHelper->getLogicMaxY(); + double MaxZ = pPosHelper->getLogicMaxZ(); + + pPosHelper->doLogicScaling( &MinX,&MinY,&MinZ ); + pPosHelper->doLogicScaling( &MaxX,&MaxY,&MaxZ ); + + if(!pPosHelper->isMathematicalOrientationX()) + { + double fHelp = MinX; + MinX = MaxX; + MaxX = fHelp; + } + if(!pPosHelper->isMathematicalOrientationY()) + { + double fHelp = MinY; + MinY = MaxY; + MaxY = fHelp; + } + if(pPosHelper->isMathematicalOrientationZ())//z axis in draw is reverse to mathematical + { + double fHelp = MinZ; + MinZ = MaxZ; + MaxZ = fHelp; + } + bool bSwapXY = pPosHelper->isSwapXAndY(); + + //P0: point on 'back' wall, not on 'left' wall + //P1: point on both walls + //P2: point on 'left' wall not on 'back' wall + + const double v0 = (eLeftWallPos == CuboidPlanePosition_Left || bSwapXY) ? MinX : MaxX; + const double v1 = (eLeftWallPos == CuboidPlanePosition_Left || !bSwapXY) ? MinY : MaxY; + const double v2 = (eBackWallPos == CuboidPlanePosition_Back) ? MinZ : MaxZ; + P0 = P1 = P2 = { v0, v1, v2 }; + + if(m_nDimensionIndex==0) + { + P0.getArray()[1] = (eLeftWallPos == CuboidPlanePosition_Left || !bSwapXY) ? MaxY : MinY; + P2.getArray()[2]= (eBackWallPos == CuboidPlanePosition_Back) ? MaxZ : MinZ; + if( eBottomPos != CuboidPlanePosition_Bottom && !bSwapXY ) + P2=P1; + } + else if(m_nDimensionIndex==1) + { + P0.getArray()[0]= (eLeftWallPos == CuboidPlanePosition_Left || bSwapXY) ? MaxX : MinX; + P2.getArray()[2]= (eBackWallPos == CuboidPlanePosition_Back) ? MaxZ : MinZ; + if( eBottomPos != CuboidPlanePosition_Bottom && bSwapXY ) + P2=P1; + } + else if(m_nDimensionIndex==2) + { + P0.getArray()[0]= (eLeftWallPos == CuboidPlanePosition_Left || bSwapXY) ? MaxX : MinX; + P2.getArray()[1]= (eLeftWallPos == CuboidPlanePosition_Left || !bSwapXY) ? MaxY : MinY; + if( eBottomPos != CuboidPlanePosition_Bottom ) + { + if( !bSwapXY ) + P0=P1; + else + P2=P1; + } + } +} + +void GridLinePoints::update( double fScaledTickValue ) +{ + P0.getArray()[m_nDimensionIndex] = P1.getArray()[m_nDimensionIndex] = P2.getArray()[m_nDimensionIndex] = fScaledTickValue; +} + +static void addLine2D( drawing::PointSequenceSequence& rPoints, sal_Int32 nIndex + , const GridLinePoints& rScaledLogicPoints + , const XTransformation2& rTransformation + ) +{ + drawing::Position3D aPA = rTransformation.transform( SequenceToPosition3D(rScaledLogicPoints.P0) ); + drawing::Position3D aPB = rTransformation.transform( SequenceToPosition3D(rScaledLogicPoints.P1) ); + + rPoints.getArray()[nIndex] + = { { static_cast<sal_Int32>(aPA.PositionX), static_cast<sal_Int32>(aPA.PositionY) }, + { static_cast<sal_Int32>(aPB.PositionX), static_cast<sal_Int32>(aPB.PositionY) } }; +} + +static void addLine3D( std::vector<std::vector<css::drawing::Position3D>>& rPoints, sal_Int32 nIndex + , const GridLinePoints& rBasePoints + , const XTransformation2 & rTransformation ) +{ + drawing::Position3D aPoint =rTransformation.transform( rBasePoints.P0 ); + AddPointToPoly( rPoints, aPoint, nIndex ); + aPoint = rTransformation.transform( rBasePoints.P1 ); + AddPointToPoly( rPoints, aPoint, nIndex ); + aPoint = rTransformation.transform( rBasePoints.P2 ); + AddPointToPoly( rPoints, aPoint, nIndex ); +} + +VCartesianGrid::VCartesianGrid( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , const std::vector< Reference< beans::XPropertySet > > aGridPropertiesList ) + : VAxisOrGridBase( nDimensionIndex, nDimensionCount ) + , m_aGridPropertiesList( std::move(aGridPropertiesList) ) +{ + m_pPosHelper = new PlottingPositionHelper(); +} + +VCartesianGrid::~VCartesianGrid() +{ + delete m_pPosHelper; + m_pPosHelper = nullptr; +} + +void VCartesianGrid::fillLinePropertiesFromGridModel( std::vector<VLineProperties>& rLinePropertiesList + , const std::vector< Reference< beans::XPropertySet > > & rGridPropertiesList ) +{ + rLinePropertiesList.clear(); + if( rGridPropertiesList.empty() ) + return; + + VLineProperties aLineProperties; + for( const auto & rxPropSet : rGridPropertiesList ) + { + if(!AxisHelper::isGridVisible( rxPropSet )) + aLineProperties.LineStyle <<= drawing::LineStyle_NONE; + else + aLineProperties.initFromPropertySet( rxPropSet ); + rLinePropertiesList.push_back(aLineProperties); + } +}; + +void VCartesianGrid::createShapes() +{ + if(m_aGridPropertiesList.empty()) + return; + //somehow equal to axis tickmarks + + //create named group shape + rtl::Reference< SvxShapeGroupAnyD > xGroupShape_Shapes( + createGroupShape( m_xLogicTarget, m_aCID ) ); + + if(!xGroupShape_Shapes.is()) + return; + + std::vector<VLineProperties> aLinePropertiesList; + fillLinePropertiesFromGridModel( aLinePropertiesList, m_aGridPropertiesList ); + + //create all scaled tickmark values + std::unique_ptr< TickFactory > apTickFactory( createTickFactory() ); + TickFactory& aTickFactory = *apTickFactory; + TickInfoArraysType aAllTickInfos; + aTickFactory.getAllTicks( aAllTickInfos ); + + //create tick mark line shapes + + if(aAllTickInfos.empty())//no tickmarks at all + return; + + TickInfoArraysType::iterator aDepthIter = aAllTickInfos.begin(); + const TickInfoArraysType::const_iterator aDepthEnd = aAllTickInfos.end(); + + sal_Int32 nLinePropertiesCount = aLinePropertiesList.size(); + for( sal_Int32 nDepth=0 + ; aDepthIter != aDepthEnd && nDepth < nLinePropertiesCount + ; ++aDepthIter, nDepth++ ) + { + if( !aLinePropertiesList[nDepth].isLineVisible() ) + continue; + + rtl::Reference< SvxShapeGroupAnyD > xTarget( xGroupShape_Shapes ); + if( nDepth > 0 ) + { + xTarget = createGroupShape( m_xLogicTarget + , ObjectIdentifier::addChildParticle( m_aCID, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_SUBGRID, nDepth-1 ) ) + ); + if(!xTarget.is()) + xTarget = xGroupShape_Shapes; + } + + if(m_nDimension==2) + { + + GridLinePoints aGridLinePoints( m_pPosHelper, m_nDimensionIndex ); + + sal_Int32 nPointCount = (*aDepthIter).size(); + drawing::PointSequenceSequence aPoints(nPointCount); + + sal_Int32 nRealPointCount = 0; + for (auto const& tick : *aDepthIter) + { + if( !tick.bPaintIt ) + continue; + aGridLinePoints.update( tick.fScaledTickValue ); + addLine2D( aPoints, nRealPointCount, aGridLinePoints, *m_pPosHelper->getTransformationScaledLogicToScene() ); + nRealPointCount++; + } + aPoints.realloc(nRealPointCount); + ShapeFactory::createLine2D( xTarget, aPoints, &aLinePropertiesList[nDepth] ); + + //prepare polygon for handle shape: + drawing::PointSequenceSequence aHandlesPoints(1); + auto pHandlesPoints = aHandlesPoints.getArray(); + pHandlesPoints[0].realloc(nRealPointCount); + auto pHandlesPoints0 = pHandlesPoints[0].getArray(); + for( sal_Int32 nN = 0; nN<nRealPointCount; nN++) + pHandlesPoints0[nN] = aPoints[nN][1]; + + //create handle shape: + VLineProperties aHandleLineProperties; + aHandleLineProperties.LineStyle <<= drawing::LineStyle_NONE; + rtl::Reference<SvxShapePolyPolygon> xHandleShape = + ShapeFactory::createLine2D( xTarget, aHandlesPoints, &aHandleLineProperties ); + ::chart::ShapeFactory::setShapeName( xHandleShape, "HandlesOnly" ); + } + else //if(2!=m_nDimension) + { + GridLinePoints aGridLinePoints( m_pPosHelper, m_nDimensionIndex, m_eLeftWallPos, m_eBackWallPos, m_eBottomPos ); + + sal_Int32 nPointCount = (*aDepthIter).size(); + std::vector<std::vector<css::drawing::Position3D>> aPoints; + aPoints.resize(nPointCount); + + sal_Int32 nRealPointCount = 0; + sal_Int32 nPolyIndex = 0; + for (auto const& tick : *aDepthIter) + { + if( !tick.bPaintIt ) + { + ++nPolyIndex; + continue; + } + + aGridLinePoints.update( tick.fScaledTickValue ); + addLine3D( aPoints, nPolyIndex, aGridLinePoints, *m_pPosHelper->getTransformationScaledLogicToScene() ); + nRealPointCount+=3; + ++nPolyIndex; + } + aPoints.resize(nRealPointCount); + ShapeFactory::createLine3D( xTarget, aPoints, aLinePropertiesList[nDepth] ); + } + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCartesianGrid.hxx b/chart2/source/view/axes/VCartesianGrid.hxx new file mode 100644 index 000000000..81ba1e16f --- /dev/null +++ b/chart2/source/view/axes/VCartesianGrid.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ +#pragma once + +#include "VAxisOrGridBase.hxx" +#include <com/sun/star/beans/XPropertySet.hpp> + +namespace chart { struct VLineProperties; } + +namespace chart +{ + +class VCartesianGrid : public VAxisOrGridBase +{ +// public methods +public: + VCartesianGrid( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , std::vector< + css::uno::Reference< css::beans::XPropertySet > > aGridPropertiesList //main grid, subgrid, subsubgrid etc + ); + virtual ~VCartesianGrid() override; + + virtual void createShapes() override; + + static void fillLinePropertiesFromGridModel( std::vector<VLineProperties>& rLinePropertiesList + , const std::vector< + css::uno::Reference< css::beans::XPropertySet > >& rGridPropertiesList ); + +private: + std::vector< + css::uno::Reference< css::beans::XPropertySet > > m_aGridPropertiesList; //main grid, subgrid, subsubgrid etc +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VCoordinateSystem.cxx b/chart2/source/view/axes/VCoordinateSystem.cxx new file mode 100644 index 000000000..25a0f5d35 --- /dev/null +++ b/chart2/source/view/axes/VCoordinateSystem.cxx @@ -0,0 +1,576 @@ +/* -*- 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 <BaseGFXHelper.hxx> +#include <DateHelper.hxx> +#include <VCoordinateSystem.hxx> +#include "VCartesianCoordinateSystem.hxx" +#include "VPolarCoordinateSystem.hxx" +#include <BaseCoordinateSystem.hxx> +#include <ChartModel.hxx> +#include <ScaleAutomatism.hxx> +#include <ShapeFactory.hxx> +#include <servicenames_coosystems.hxx> +#include <ObjectIdentifier.hxx> +#include <ExplicitCategoriesProvider.hxx> +#include <Axis.hxx> +#include "VAxisBase.hxx" +#include <defines.hxx> +#include <chartview/ExplicitValueProvider.hxx> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <comphelper/sequence.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <algorithm> +#include <limits> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; + +std::unique_ptr<VCoordinateSystem> VCoordinateSystem::createCoordinateSystem( + const rtl::Reference< BaseCoordinateSystem >& xCooSysModel ) +{ + if( !xCooSysModel.is() ) + return nullptr; + + OUString aViewServiceName = xCooSysModel->getViewServiceName(); + + //@todo: in future the coordinatesystems should be instantiated via service factory + std::unique_ptr<VCoordinateSystem> pRet; + if( aViewServiceName == CHART2_COOSYSTEM_CARTESIAN_VIEW_SERVICE_NAME ) + pRet.reset( new VCartesianCoordinateSystem(xCooSysModel) ); + else if( aViewServiceName == CHART2_COOSYSTEM_POLAR_VIEW_SERVICE_NAME ) + pRet.reset( new VPolarCoordinateSystem(xCooSysModel) ); + if(!pRet) + pRet.reset( new VCoordinateSystem(xCooSysModel) ); + return pRet; +} + +VCoordinateSystem::VCoordinateSystem( const rtl::Reference< BaseCoordinateSystem >& xCooSys ) + : m_xCooSysModel(xCooSys) + , m_eLeftWallPos(CuboidPlanePosition_Left) + , m_eBackWallPos(CuboidPlanePosition_Back) + , m_eBottomPos(CuboidPlanePosition_Bottom) + , m_aExplicitScales(3) + , m_aExplicitIncrements(3) +{ + if( !m_xCooSysModel.is() || m_xCooSysModel->getDimension()<3 ) + { + m_aExplicitScales[2].Minimum = 1.0; + m_aExplicitScales[2].Maximum = 2.0; + m_aExplicitScales[2].Orientation = AxisOrientation_MATHEMATICAL; + } +} +VCoordinateSystem::~VCoordinateSystem() +{ +} + +void VCoordinateSystem::initPlottingTargets( const rtl::Reference< SvxShapeGroupAnyD >& xLogicTarget + , const rtl::Reference< SvxShapeGroupAnyD >& xFinalTarget + , rtl::Reference<SvxShapeGroupAnyD>& xLogicTargetForSeriesBehindAxis ) +{ + OSL_PRECOND(xLogicTarget.is()&&xFinalTarget.is(),"no proper initialization parameters"); + //is only allowed to be called once + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + //create group shape for grids first thus axes are always painted above grids + if(nDimensionCount==2) + { + //create and add to target + m_xLogicTargetForGrids = ShapeFactory::createGroup2D( xLogicTarget ); + xLogicTargetForSeriesBehindAxis = ShapeFactory::createGroup2D( xLogicTarget ); + m_xLogicTargetForAxes = ShapeFactory::createGroup2D( xLogicTarget ); + } + else + { + //create and added to target + m_xLogicTargetForGrids = ShapeFactory::createGroup3D( xLogicTarget ); + xLogicTargetForSeriesBehindAxis = ShapeFactory::createGroup3D( xLogicTarget ); + m_xLogicTargetForAxes = ShapeFactory::createGroup3D( xLogicTarget ); + } + m_xFinalTarget = xFinalTarget; +} + +void VCoordinateSystem::setParticle( const OUString& rCooSysParticle ) +{ + m_aCooSysParticle = rCooSysParticle; +} + +void VCoordinateSystem::setTransformationSceneToScreen( + const drawing::HomogenMatrix& rMatrix ) +{ + m_aMatrixSceneToScreen = rMatrix; + + //correct transformation for axis + for (auto const& elem : m_aAxisMap) + { + VAxisBase* pVAxis = elem.second.get(); + if( pVAxis ) + { + if(pVAxis->getDimensionCount()==2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + } + } +} + +//better performance for big data +uno::Sequence< sal_Int32 > VCoordinateSystem::getCoordinateSystemResolution( + const awt::Size& rPageSize, const awt::Size& rPageResolution ) +{ + uno::Sequence<sal_Int32> aResolution( + std::max<sal_Int32>(m_xCooSysModel->getDimension(), 2)); + auto aResolutionRange = asNonConstRange(aResolution); + for( auto& i : aResolutionRange ) + i = 1000; + + ::basegfx::B3DTuple aScale( BaseGFXHelper::GetScaleFromMatrix( + BaseGFXHelper::HomogenMatrixToB3DHomMatrix( + m_aMatrixSceneToScreen ) ) ); + + double fCoosysWidth = fabs(aScale.getX()*FIXED_SIZE_FOR_3D_CHART_VOLUME); + double fCoosysHeight = fabs(aScale.getY()*FIXED_SIZE_FOR_3D_CHART_VOLUME); + + double fPageWidth = rPageSize.Width; + double fPageHeight = rPageSize.Height; + + //factor 2 to avoid rounding problems + sal_Int32 nXResolution = static_cast<sal_Int32>(2.0*static_cast<double>(rPageResolution.Width)*fCoosysWidth/fPageWidth); + sal_Int32 nYResolution = static_cast<sal_Int32>(2.0*static_cast<double>(rPageResolution.Height)*fCoosysHeight/fPageHeight); + + if( nXResolution < 10 ) + nXResolution = 10; + if( nYResolution < 10 ) + nYResolution = 10; + + if( getPropertySwapXAndYAxis() ) + std::swap(nXResolution,nYResolution); + + //2D + if( aResolution.getLength() == 2 ) + { + aResolutionRange[0]=nXResolution; + aResolutionRange[1]=nYResolution; + } + else + { + //this maybe can be optimized further ... + sal_Int32 nMaxResolution = std::max( nXResolution, nYResolution ); + nMaxResolution*=2; + for( auto& i : asNonConstRange(aResolution) ) + i = nMaxResolution; + } + + return aResolution; +} + +rtl::Reference< Axis > VCoordinateSystem::getAxisByDimension( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const +{ + if( m_xCooSysModel.is() ) + return m_xCooSysModel->getAxisByDimension2( nDimensionIndex, nAxisIndex ); + return nullptr; +} + +std::vector< Reference< beans::XPropertySet > > VCoordinateSystem::getGridListFromAxis( const rtl::Reference< Axis >& xAxis ) +{ + std::vector< Reference< beans::XPropertySet > > aRet; + + if( xAxis.is() ) + { + aRet.push_back( xAxis->getGridProperties() ); + auto aSubGrids( comphelper::sequenceToContainer<std::vector< Reference< beans::XPropertySet > > >( xAxis->getSubGridProperties() ) ); + aRet.insert( aRet.end(), aSubGrids.begin(), aSubGrids.end() ); + } + + return aRet; +} + +void VCoordinateSystem::impl_adjustDimension( sal_Int32& rDimensionIndex ) +{ + rDimensionIndex = std::clamp<sal_Int32>(rDimensionIndex, 0, 2); +} + +void VCoordinateSystem::impl_adjustDimensionAndIndex( sal_Int32& rDimensionIndex, sal_Int32& rAxisIndex ) const +{ + impl_adjustDimension( rDimensionIndex ); + + if( rAxisIndex < 0 || rAxisIndex > getMaximumAxisIndexByDimension(rDimensionIndex) ) + rAxisIndex = 0; +} + +void VCoordinateSystem::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider /*takes ownership*/ ) +{ + m_apExplicitCategoriesProvider.reset(pExplicitCategoriesProvider); +} + +ExplicitCategoriesProvider* VCoordinateSystem::getExplicitCategoriesProvider() +{ + return m_apExplicitCategoriesProvider.get(); +} + +std::vector< ExplicitScaleData > VCoordinateSystem::getExplicitScales( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const +{ + std::vector< ExplicitScaleData > aRet(m_aExplicitScales); + + impl_adjustDimensionAndIndex( nDimensionIndex, nAxisIndex ); + aRet[nDimensionIndex]=getExplicitScale( nDimensionIndex, nAxisIndex ); + + return aRet; +} + +std::vector< ExplicitIncrementData > VCoordinateSystem::getExplicitIncrements( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const +{ + std::vector< ExplicitIncrementData > aRet(m_aExplicitIncrements); + + impl_adjustDimensionAndIndex( nDimensionIndex, nAxisIndex ); + aRet[nDimensionIndex]=getExplicitIncrement( nDimensionIndex, nAxisIndex ); + + return aRet; +} + +ExplicitScaleData VCoordinateSystem::getExplicitScale( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const +{ + ExplicitScaleData aRet; + + impl_adjustDimensionAndIndex( nDimensionIndex, nAxisIndex ); + + if( nAxisIndex == 0) + { + aRet = m_aExplicitScales[nDimensionIndex]; + } + else + { + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + tFullExplicitScaleMap::const_iterator aIt = m_aSecondaryExplicitScales.find( aFullAxisIndex ); + if( aIt != m_aSecondaryExplicitScales.end() ) + aRet = aIt->second; + else + aRet = m_aExplicitScales[nDimensionIndex]; + } + + return aRet; +} + +ExplicitIncrementData VCoordinateSystem::getExplicitIncrement( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const +{ + ExplicitIncrementData aRet; + + impl_adjustDimensionAndIndex( nDimensionIndex, nAxisIndex ); + + if( nAxisIndex == 0) + { + aRet = m_aExplicitIncrements[nDimensionIndex]; + } + else + { + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + tFullExplicitIncrementMap::const_iterator aIt = m_aSecondaryExplicitIncrements.find( aFullAxisIndex ); + if( aIt != m_aSecondaryExplicitIncrements.end() ) + aRet = aIt->second; + else + aRet = m_aExplicitIncrements[nDimensionIndex]; + } + + return aRet; +} + +OUString VCoordinateSystem::createCIDForAxis( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) +{ + OUString aAxisParticle( ObjectIdentifier::createParticleForAxis( nDimensionIndex, nAxisIndex ) ); + return ObjectIdentifier::createClassifiedIdentifierForParticles( m_aCooSysParticle, aAxisParticle ); +} +OUString VCoordinateSystem::createCIDForGrid( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) +{ + OUString aGridParticle( ObjectIdentifier::createParticleForGrid( nDimensionIndex, nAxisIndex ) ); + return ObjectIdentifier::createClassifiedIdentifierForParticles( m_aCooSysParticle, aGridParticle ); +} + +sal_Int32 VCoordinateSystem::getMaximumAxisIndexByDimension( sal_Int32 nDimensionIndex ) const +{ + sal_Int32 nRet = 0; + for (auto const& elem : m_aSecondaryExplicitScales) + { + if(elem.first.first==nDimensionIndex) + { + sal_Int32 nLocalIdx = elem.first.second; + if( nRet < nLocalIdx ) + nRet = nLocalIdx; + } + } + return nRet; +} + +void VCoordinateSystem::createVAxisList( + const rtl::Reference<::chart::ChartModel> & /* xChartDoc */ + , const awt::Size& /* rFontReferenceSize */ + , const awt::Rectangle& /* rMaximumSpaceForLabels */ + , bool /* bLimitSpaceForLabels */ + ) +{ +} + +void VCoordinateSystem::initVAxisInList() +{ +} +void VCoordinateSystem::updateScalesAndIncrementsOnAxes() +{ +} + +void VCoordinateSystem::prepareAutomaticAxisScaling( ScaleAutomatism& rScaleAutomatism, sal_Int32 nDimIndex, sal_Int32 nAxisIndex ) +{ + bool bDateAxisX = (rScaleAutomatism.getScale().AxisType == AxisType::DATE) && (nDimIndex == 0); + if( bDateAxisX ) + { + // This is a date X dimension. Determine proper time resolution. + sal_Int32 nTimeResolution = css::chart::TimeUnit::MONTH; + if( !(rScaleAutomatism.getScale().TimeIncrement.TimeResolution >>= nTimeResolution) ) + { + nTimeResolution = m_aMergedMinMaxSupplier.calculateTimeResolutionOnXAxis(); + rScaleAutomatism.setAutomaticTimeResolution( nTimeResolution ); + } + m_aMergedMinMaxSupplier.setTimeResolutionOnXAxis( nTimeResolution, rScaleAutomatism.getNullDate() ); + } + + double fMin = std::numeric_limits<double>::infinity(); + double fMax = -std::numeric_limits<double>::infinity(); + if( nDimIndex == 0 ) + { + // x dimension + fMin = m_aMergedMinMaxSupplier.getMinimumX(); + fMax = m_aMergedMinMaxSupplier.getMaximumX(); + } + else if( nDimIndex == 1 ) + { + // y dimension + ExplicitScaleData aScale = getExplicitScale( 0, 0 ); + double fMaximum = aScale.Maximum; + if (!aScale.m_bShiftedCategoryPosition && aScale.AxisType == AxisType::DATE) + { + // tdf#146066 Increase maximum date value by one month/year, + // because the automatic scaling of the Y axis was incorrect when the last Y value was the highest value. + Date aMaxDate(aScale.NullDate); + aMaxDate.AddDays(::rtl::math::approxFloor(fMaximum)); + switch (aScale.TimeResolution) + { + case css::chart::TimeUnit::MONTH: + aMaxDate = DateHelper::GetDateSomeMonthsAway(aMaxDate, 1); + break; + case css::chart::TimeUnit::YEAR: + aMaxDate = DateHelper::GetDateSomeYearsAway(aMaxDate, 1); + break; + } + fMaximum = aMaxDate - aScale.NullDate; + } + fMin = m_aMergedMinMaxSupplier.getMinimumYInRange(aScale.Minimum,aScale.Maximum, nAxisIndex); + fMax = m_aMergedMinMaxSupplier.getMaximumYInRange(aScale.Minimum, fMaximum, nAxisIndex); + } + else if( nDimIndex == 2 ) + { + // z dimension + fMin = m_aMergedMinMaxSupplier.getMinimumZ(); + fMax = m_aMergedMinMaxSupplier.getMaximumZ(); + } + + //merge our values with those already contained in rScaleAutomatism + rScaleAutomatism.expandValueRange( fMin, fMax ); + + rScaleAutomatism.setAutoScalingOptions( + m_aMergedMinMaxSupplier.isExpandBorderToIncrementRhythm( nDimIndex ), + m_aMergedMinMaxSupplier.isExpandIfValuesCloseToBorder( nDimIndex ), + m_aMergedMinMaxSupplier.isExpandWideValuesToZero( nDimIndex ), + m_aMergedMinMaxSupplier.isExpandNarrowValuesTowardZero( nDimIndex ) ); + + if (bDateAxisX) + return; + + VAxisBase* pVAxis = getVAxis(nDimIndex, nAxisIndex); + if( pVAxis ) + rScaleAutomatism.setMaximumAutoMainIncrementCount( pVAxis->estimateMaximumAutoMainIncrementCount() ); +} + +VAxisBase* VCoordinateSystem::getVAxis( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) +{ + VAxisBase* pRet = nullptr; + + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + + tVAxisMap::const_iterator aIt = m_aAxisMap.find( aFullAxisIndex ); + if (aIt != m_aAxisMap.cend()) + pRet = aIt->second.get(); + + return pRet; +} + +void VCoordinateSystem::setExplicitScaleAndIncrement( + sal_Int32 nDimensionIndex + , sal_Int32 nAxisIndex + , const ExplicitScaleData& rExplicitScale + , const ExplicitIncrementData& rExplicitIncrement ) +{ + impl_adjustDimension( nDimensionIndex ); + + if( nAxisIndex==0 ) + { + m_aExplicitScales[nDimensionIndex]=rExplicitScale; + m_aExplicitIncrements[nDimensionIndex]=rExplicitIncrement; + } + else + { + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + m_aSecondaryExplicitScales[aFullAxisIndex] = rExplicitScale; + m_aSecondaryExplicitIncrements[aFullAxisIndex] = rExplicitIncrement; + } +} + +void VCoordinateSystem::set3DWallPositions( CuboidPlanePosition eLeftWallPos, CuboidPlanePosition eBackWallPos, CuboidPlanePosition eBottomPos ) +{ + m_eLeftWallPos = eLeftWallPos; + m_eBackWallPos = eBackWallPos; + m_eBottomPos = eBottomPos; +} + +void VCoordinateSystem::createMaximumAxesLabels() +{ + for (auto const&[unused, pVAxis] : m_aAxisMap) + { + (void)unused; + if (pVAxis) + { + if (pVAxis->getDimensionCount() == 2) + pVAxis->setTransformationSceneToScreen(m_aMatrixSceneToScreen); + pVAxis->createMaximumLabels(); + } + } +} +void VCoordinateSystem::createAxesLabels() +{ + for (auto const&[unused, pVAxis] : m_aAxisMap) + { + (void)unused; + if (pVAxis) + { + if (pVAxis->getDimensionCount() == 2) + pVAxis->setTransformationSceneToScreen(m_aMatrixSceneToScreen); + pVAxis->createLabels(); + } + } +} + +void VCoordinateSystem::updatePositions() +{ + for (auto const&[unused, pVAxis] : m_aAxisMap) + { + (void)unused; + if (pVAxis) + { + if (pVAxis->getDimensionCount() == 2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + pVAxis->updatePositions(); + } + } +} + +void VCoordinateSystem::createAxesShapes() +{ + for (auto const&[aFullAxisIndex, pVAxis] : m_aAxisMap) + { + if (pVAxis) + { + auto const&[nDimensionIndex, nAxisIndex] = aFullAxisIndex; + + if (pVAxis->getDimensionCount() == 2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + + if (nAxisIndex == 0) + { + if (nDimensionIndex == 0) + { + if( m_aExplicitScales[1].AxisType!=AxisType::CATEGORY ) + pVAxis->setExtraLinePositionAtOtherAxis( + m_aExplicitScales[1].Origin ); + } + else if (nDimensionIndex == 1) + { + if( m_aExplicitScales[0].AxisType!=AxisType::CATEGORY ) + pVAxis->setExtraLinePositionAtOtherAxis( + m_aExplicitScales[0].Origin ); + } + } + + pVAxis->createShapes(); + } + } +} +void VCoordinateSystem::createGridShapes() +{ +} +void VCoordinateSystem::addMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier ) +{ + m_aMergedMinMaxSupplier.addMinimumAndMaximumSupplier(pMinimumAndMaximumSupplier); +} + +bool VCoordinateSystem::hasMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier ) +{ + return m_aMergedMinMaxSupplier.hasMinimumAndMaximumSupplier(pMinimumAndMaximumSupplier); +} + +void VCoordinateSystem::clearMinimumAndMaximumSupplierList() +{ + m_aMergedMinMaxSupplier.clearMinimumAndMaximumSupplierList(); +} + +bool VCoordinateSystem::getPropertySwapXAndYAxis() const +{ + bool bSwapXAndY = false; + if( m_xCooSysModel.is()) try + { + m_xCooSysModel->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return bSwapXAndY; +} + +bool VCoordinateSystem::needSeriesNamesForAxis() const +{ + return ( m_xCooSysModel.is() && m_xCooSysModel->getDimension() == 3 ); +} +void VCoordinateSystem::setSeriesNamesForAxis( const Sequence< OUString >& rSeriesNames ) +{ + m_aSeriesNamesForZAxis = rSeriesNames; +} + +sal_Int32 VCoordinateSystem::getNumberFormatKeyForAxis( + const rtl::Reference< Axis >& xAxis + , const rtl::Reference<::chart::ChartModel>& xChartDoc) +{ + return ExplicitValueProvider::getExplicitNumberFormatKeyForAxis( + xAxis, m_xCooSysModel, xChartDoc); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarAngleAxis.cxx b/chart2/source/view/axes/VPolarAngleAxis.cxx new file mode 100644 index 000000000..13f66cec9 --- /dev/null +++ b/chart2/source/view/axes/VPolarAngleAxis.cxx @@ -0,0 +1,205 @@ +/* -*- 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 <basegfx/numeric/ftools.hxx> + +#include "VPolarAngleAxis.hxx" +#include "VPolarGrid.hxx" +#include <ShapeFactory.hxx> +#include <Axis.hxx> +#include <NumberFormatterWrapper.hxx> +#include <PolarLabelPositionHelper.hxx> +#include <PlottingPositionHelper.hxx> +#include <tools/color.hxx> + +#include <memory> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +VPolarAngleAxis::VPolarAngleAxis( const AxisProperties& rAxisProperties + , const uno::Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionCount ) + : VPolarAxis( rAxisProperties, xNumberFormatsSupplier, 0/*nDimensionIndex*/, nDimensionCount ) +{ +} + +VPolarAngleAxis::~VPolarAngleAxis() +{ +} + +void VPolarAngleAxis::createTextShapes_ForAngleAxis( + const rtl::Reference<SvxShapeGroupAnyD>& xTarget + , EquidistantTickIter& rTickIter + , AxisLabelProperties const & rAxisLabelProperties + , double fLogicRadius + , double fLogicZ ) +{ + FixedNumberFormatter aFixedNumberFormatter( + m_xNumberFormatsSupplier, rAxisLabelProperties.m_nNumberFormatKey ); + + //prepare text properties for multipropertyset-interface of shape + tNameSequence aPropNames; + tAnySequence aPropValues; + + uno::Reference< beans::XPropertySet > xProps( m_aAxisProperties.m_xAxisModel ); + PropertyMapper::getTextLabelMultiPropertyLists( xProps, aPropNames, aPropValues, false, -1, false, false ); + LabelPositionHelper::doDynamicFontResize( aPropValues, aPropNames, xProps + , rAxisLabelProperties.m_aFontReferenceSize ); + + uno::Any* pColorAny = PropertyMapper::getValuePointer(aPropValues,aPropNames,u"CharColor"); + Color nColor = COL_AUTO; + if(pColorAny) + *pColorAny >>= nColor; + + const uno::Sequence< OUString >* pLabels = m_bUseTextLabels? &m_aTextLabels : nullptr; + + //TickInfo* pLastVisibleNeighbourTickInfo = NULL; + sal_Int32 nTick = 0; + + for( TickInfo* pTickInfo = rTickIter.firstInfo() + ; pTickInfo + ; pTickInfo = rTickIter.nextInfo(), nTick++ ) + { + //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 NO OVERLAP -> don't create labels where the + //anchor position is the same as for the last label + //@todo + + if(!pTickInfo->xTextShape.is()) + { + //create single label + bool bHasExtraColor=false; + Color nExtraColor; + + OUString aLabel; + if(pLabels) + { + sal_Int32 nIndex = static_cast< sal_Int32 >(pTickInfo->getUnscaledTickValue()) - 1; //first category (index 0) matches with real number 1.0 + if( nIndex>=0 && nIndex<pLabels->getLength() ) + aLabel = (*pLabels)[nIndex]; + } + else + aLabel = aFixedNumberFormatter.getFormattedString( pTickInfo->getUnscaledTickValue(), nExtraColor, bHasExtraColor ); + + if(pColorAny) + *pColorAny <<= bHasExtraColor?nExtraColor:nColor; + + double fLogicAngle = pTickInfo->getUnscaledTickValue(); + + LabelAlignment eLabelAlignment(LABEL_ALIGN_CENTER); + PolarLabelPositionHelper aPolarLabelPositionHelper(m_pPosHelper.get(), 2/*nDimensionCount*/, xTarget); + sal_Int32 nScreenValueOffsetInRadiusDirection = m_aAxisLabelProperties.m_aMaximumSpaceForLabels.Height/15; + awt::Point aAnchorScreenPosition2D( aPolarLabelPositionHelper.getLabelScreenPositionAndAlignmentForLogicValues( + eLabelAlignment, fLogicAngle, fLogicRadius, fLogicZ, nScreenValueOffsetInRadiusDirection )); + LabelPositionHelper::changeTextAdjustment( aPropValues, aPropNames, eLabelAlignment ); + + // #i78696# use mathematically correct rotation now + const double fRotationAnglePi(-basegfx::deg2rad(rAxisLabelProperties.m_fRotationAngleDegree)); + + uno::Any aATransformation = ShapeFactory::makeTransformation( aAnchorScreenPosition2D, fRotationAnglePi ); + OUString aStackedLabel = ShapeFactory::getStackedString( aLabel, rAxisLabelProperties.m_bStackCharacters ); + + pTickInfo->xTextShape = ShapeFactory::createText( xTarget, aStackedLabel, aPropNames, aPropValues, aATransformation ); + } + + //if NO OVERLAP -> remove overlapping shapes + //@todo + } +} + +void VPolarAngleAxis::createMaximumLabels() +{ + if( !prepareShapeCreation() ) + return; + + createLabels(); +} + +void VPolarAngleAxis::updatePositions() +{ + //todo: really only update the positions + + if( !prepareShapeCreation() ) + return; + + createLabels(); +} + +void VPolarAngleAxis::createLabels() +{ + if( !prepareShapeCreation() ) + return; + + double fLogicRadius = m_pPosHelper->getOuterLogicRadius(); + + if( !m_aAxisProperties.m_bDisplayLabels ) + return; + + //create tick mark text shapes + //@todo: iterate through all tick depth which should be labeled + + EquidistantTickIter aTickIter( m_aAllTickInfos, m_aIncrement, 0 ); + updateUnscaledValuesAtTicks( aTickIter ); + + removeTextShapesFromTicks(); + + AxisLabelProperties aAxisLabelProperties( m_aAxisLabelProperties ); + aAxisLabelProperties.m_bOverlapAllowed = true; + double const fLogicZ = 1.0;//as defined + createTextShapes_ForAngleAxis( m_xTextTarget, aTickIter + , aAxisLabelProperties + , fLogicRadius, fLogicZ + ); + + //no staggering for polar angle axis +} + +void VPolarAngleAxis::createShapes() +{ + if( !prepareShapeCreation() ) + return; + + double fLogicRadius = m_pPosHelper->getOuterLogicRadius(); + double const fLogicZ = 1.0;//as defined + + //create axis main lines + drawing::PointSequenceSequence aPoints(1); + VPolarGrid::createLinePointSequence_ForAngleAxis( aPoints, m_aAllTickInfos, m_aIncrement, m_aScale, m_pPosHelper.get(), fLogicRadius, fLogicZ ); + 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, "MarkHandles" ); + + //create labels + createLabels(); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarAngleAxis.hxx b/chart2/source/view/axes/VPolarAngleAxis.hxx new file mode 100644 index 000000000..0e0774e9e --- /dev/null +++ b/chart2/source/view/axes/VPolarAngleAxis.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ +#pragma once + +#include "VPolarAxis.hxx" +#include "Tickmarks_Equidistant.hxx" + +namespace chart +{ + +class VPolarAngleAxis : public VPolarAxis +{ +public: + VPolarAngleAxis( const AxisProperties& rAxisProperties + , const css::uno::Reference< css::util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionCount ); + virtual ~VPolarAngleAxis() override; + + virtual void createMaximumLabels() override; + virtual void createLabels() override; + virtual void updatePositions() override; + + virtual void createShapes() override; + +private: //methods + void createTextShapes_ForAngleAxis( + const rtl::Reference<SvxShapeGroupAnyD>& xTarget + , EquidistantTickIter& rTickIter + , AxisLabelProperties const & rAxisLabelProperties + , double fLogicRadius, double fLogicZ ); +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarAxis.cxx b/chart2/source/view/axes/VPolarAxis.cxx new file mode 100644 index 000000000..d9dfe08ec --- /dev/null +++ b/chart2/source/view/axes/VPolarAxis.cxx @@ -0,0 +1,64 @@ +/* -*- 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 "VPolarAxis.hxx" +#include "VPolarAngleAxis.hxx" +#include "VPolarRadiusAxis.hxx" +#include <PlottingPositionHelper.hxx> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +std::shared_ptr<VPolarAxis> VPolarAxis::createAxis( const AxisProperties& rAxisProperties + , const uno::Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount ) +{ + if( nDimensionIndex==0 ) + return std::make_shared<VPolarAngleAxis>( rAxisProperties, xNumberFormatsSupplier, nDimensionCount ); + return std::make_shared<VPolarRadiusAxis>( rAxisProperties, xNumberFormatsSupplier, nDimensionCount ); +} + +VPolarAxis::VPolarAxis( const AxisProperties& rAxisProperties + , const uno::Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount ) + : VAxisBase( nDimensionIndex, nDimensionCount, rAxisProperties, xNumberFormatsSupplier ) + , m_pPosHelper( new PolarPlottingPositionHelper() ) +{ + PlotterBase::m_pPosHelper = m_pPosHelper.get(); +} + +VPolarAxis::~VPolarAxis() +{ +} + +void VPolarAxis::setIncrements( std::vector< ExplicitIncrementData >&& rIncrements ) +{ + m_aIncrements = std::move(rIncrements); +} + +bool VPolarAxis::isAnythingToDraw() +{ + return ( m_nDimension==2 && VAxisBase::isAnythingToDraw() ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarAxis.hxx b/chart2/source/view/axes/VPolarAxis.hxx new file mode 100644 index 000000000..42e22ae7d --- /dev/null +++ b/chart2/source/view/axes/VPolarAxis.hxx @@ -0,0 +1,54 @@ +/* -*- 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 . + */ +#pragma once + +#include "VAxisBase.hxx" +#include <memory> + +namespace chart +{ + +class PolarPlottingPositionHelper; + +class VPolarAxis : public VAxisBase +{ +public: + static std::shared_ptr<VPolarAxis> createAxis( const AxisProperties& rAxisProperties + , const css::uno::Reference< css::util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount ); + + void setIncrements( std::vector< ExplicitIncrementData >&& rIncrements ); + + virtual bool isAnythingToDraw() override; + + virtual ~VPolarAxis() override; + +protected: + VPolarAxis( const AxisProperties& rAxisProperties + , const css::uno::Reference< css::util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount ); + +protected: //member + std::unique_ptr<PolarPlottingPositionHelper> m_pPosHelper; + std::vector< ExplicitIncrementData > m_aIncrements; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarCoordinateSystem.cxx b/chart2/source/view/axes/VPolarCoordinateSystem.cxx new file mode 100644 index 000000000..e287120f9 --- /dev/null +++ b/chart2/source/view/axes/VPolarCoordinateSystem.cxx @@ -0,0 +1,190 @@ +/* -*- 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 "VPolarCoordinateSystem.hxx" +#include "VPolarGrid.hxx" +#include "VPolarAxis.hxx" +#include <BaseCoordinateSystem.hxx> +#include <AxisIndexDefines.hxx> +#include <Axis.hxx> +#include <AxisHelper.hxx> +#include <ChartModel.hxx> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; + +VPolarCoordinateSystem::VPolarCoordinateSystem( const rtl::Reference< BaseCoordinateSystem >& xCooSys ) + : VCoordinateSystem(xCooSys) +{ +} + +VPolarCoordinateSystem::~VPolarCoordinateSystem() +{ +} + +//better performance for big data +uno::Sequence< sal_Int32 > VPolarCoordinateSystem::getCoordinateSystemResolution( + const awt::Size& rPageSize, const awt::Size& rPageResolution ) +{ + uno::Sequence< sal_Int32 > aResolution( VCoordinateSystem::getCoordinateSystemResolution( rPageSize, rPageResolution) ); + + if( aResolution.getLength() >= 2 ) + { + auto pResolution = aResolution.getArray(); + if( getPropertySwapXAndYAxis() ) + { + pResolution[0]/=2;//radius + pResolution[1]*=4;//outer circle resolution + } + else + { + pResolution[0]*=4;//outer circle resolution + pResolution[1]/=2;//radius + } + } + + return aResolution; +} + +void VPolarCoordinateSystem::createVAxisList( + const rtl::Reference<::chart::ChartModel> & xChartDoc + , const awt::Size& rFontReferenceSize + , const awt::Rectangle& rMaximumSpaceForLabels + , bool //bLimitSpaceForLabels + ) +{ + // note: using xChartDoc itself as XNumberFormatsSupplier would cause + // a leak from VPolarAxis due to cyclic reference + uno::Reference<util::XNumberFormatsSupplier> const xNumberFormatsSupplier( + dynamic_cast<ChartModel&>(*xChartDoc).getNumberFormatsSupplier()); + + m_aAxisMap.clear(); + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + sal_Int32 nDimensionIndex = 0; + + //create angle axis (dimension index 0) + for( nDimensionIndex = 0; nDimensionIndex < nDimensionCount; nDimensionIndex++ ) + { + sal_Int32 nMaxAxisIndex = m_xCooSysModel->getMaximumAxisIndexByDimension(nDimensionIndex); + for( sal_Int32 nAxisIndex = 0; nAxisIndex <= nMaxAxisIndex; nAxisIndex++ ) + { + rtl::Reference< Axis > xAxis = getAxisByDimension(nDimensionIndex,nAxisIndex); + if(!xAxis.is() || !AxisHelper::shouldAxisBeDisplayed( xAxis, m_xCooSysModel )) + continue; + AxisProperties aAxisProperties(xAxis,getExplicitCategoriesProvider()); + aAxisProperties.init(); + if(aAxisProperties.m_bDisplayLabels) + aAxisProperties.m_nNumberFormatKey = getNumberFormatKeyForAxis(xAxis, xChartDoc); + + std::shared_ptr< VAxisBase > apVAxis( VPolarAxis::createAxis( aAxisProperties,xNumberFormatsSupplier,nDimensionIndex,nDimensionCount) ); + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + m_aAxisMap[aFullAxisIndex] = apVAxis; + + apVAxis->initAxisLabelProperties(rFontReferenceSize,rMaximumSpaceForLabels); + } + } +} + +void VPolarCoordinateSystem::initVAxisInList() +{ + if(!m_xLogicTargetForAxes.is() || !m_xFinalTarget.is() || !m_xCooSysModel.is() ) + return; + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + for (auto const& elem : m_aAxisMap) + { + VAxisBase* pVAxis = elem.second.get(); + if( pVAxis ) + { + sal_Int32 nDimensionIndex = elem.first.first; + sal_Int32 nAxisIndex = elem.first.second; + pVAxis->setExplicitScaleAndIncrement( getExplicitScale( nDimensionIndex, nAxisIndex ), getExplicitIncrement(nDimensionIndex, nAxisIndex) ); + pVAxis->initPlotter(m_xLogicTargetForAxes,m_xFinalTarget + , createCIDForAxis( nDimensionIndex, nAxisIndex ) ); + VPolarAxis* pVPolarAxis = dynamic_cast< VPolarAxis* >( pVAxis ); + if( pVPolarAxis ) + pVPolarAxis->setIncrements( getExplicitIncrements( nDimensionIndex, nAxisIndex ) ); + if(nDimensionCount==2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + pVAxis->setScales( getExplicitScales( nDimensionIndex, nAxisIndex ), bSwapXAndY ); + } + } +} + +void VPolarCoordinateSystem::updateScalesAndIncrementsOnAxes() +{ + if(!m_xLogicTargetForAxes.is() || !m_xFinalTarget.is() || !m_xCooSysModel.is() ) + return; + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + for (auto const& elem : m_aAxisMap) + { + VAxisBase* pVAxis = elem.second.get(); + if( pVAxis ) + { + sal_Int32 nDimensionIndex = elem.first.first; + sal_Int32 nAxisIndex = elem.first.second; + pVAxis->setExplicitScaleAndIncrement( getExplicitScale( nDimensionIndex, nAxisIndex ), getExplicitIncrement(nDimensionIndex, nAxisIndex) ); + VPolarAxis* pVPolarAxis = dynamic_cast< VPolarAxis* >( pVAxis ); + if( pVPolarAxis ) + pVPolarAxis->setIncrements( getExplicitIncrements( nDimensionIndex, nAxisIndex ) ); + if(nDimensionCount==2) + pVAxis->setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + pVAxis->setScales( getExplicitScales( nDimensionIndex, nAxisIndex ), bSwapXAndY ); + } + } +} + +void VPolarCoordinateSystem::createGridShapes() +{ + if(!m_xLogicTargetForGrids.is() || !m_xFinalTarget.is() ) + return; + + sal_Int32 nDimensionCount = m_xCooSysModel->getDimension(); + bool bSwapXAndY = getPropertySwapXAndYAxis(); + + for( sal_Int32 nDimensionIndex=0; nDimensionIndex<3; nDimensionIndex++) + { + sal_Int32 nAxisIndex = MAIN_AXIS_INDEX; + + rtl::Reference< Axis > xAxis = AxisHelper::getAxis( nDimensionIndex, nAxisIndex, m_xCooSysModel ); + if(!xAxis.is() || !AxisHelper::shouldAxisBeDisplayed( xAxis, m_xCooSysModel )) + continue; + + VPolarGrid aGrid(nDimensionIndex,nDimensionCount,getGridListFromAxis( xAxis )); + aGrid.setIncrements( getExplicitIncrements( nDimensionIndex, nAxisIndex ) ); + aGrid.initPlotter(m_xLogicTargetForGrids,m_xFinalTarget + , createCIDForGrid( nDimensionIndex, nAxisIndex ) ); + if(nDimensionCount==2) + aGrid.setTransformationSceneToScreen( m_aMatrixSceneToScreen ); + aGrid.setScales( getExplicitScales( nDimensionIndex, nAxisIndex), bSwapXAndY ); + aGrid.createShapes(); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarCoordinateSystem.hxx b/chart2/source/view/axes/VPolarCoordinateSystem.hxx new file mode 100644 index 000000000..9659660a5 --- /dev/null +++ b/chart2/source/view/axes/VPolarCoordinateSystem.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ +#pragma once + +#include <VCoordinateSystem.hxx> + +namespace chart +{ + +class VPolarCoordinateSystem : public VCoordinateSystem +{ +public: + VPolarCoordinateSystem() = delete; + explicit VPolarCoordinateSystem( const rtl::Reference< ::chart::BaseCoordinateSystem >& xCooSys ); + virtual ~VPolarCoordinateSystem() override; + + //better performance for big data + virtual css::uno::Sequence< sal_Int32 > getCoordinateSystemResolution( const css::awt::Size& rPageSize + , const css::awt::Size& rPageResolution ) override; + + virtual void createVAxisList( + const rtl::Reference<::chart::ChartModel> & xChartDoc + , const css::awt::Size& rFontReferenceSize + , const css::awt::Rectangle& rMaximumSpaceForLabels + , bool bLimitSpaceForLabels ) override; + + virtual void initVAxisInList() override; + virtual void updateScalesAndIncrementsOnAxes() override; + + virtual void createGridShapes() override; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarGrid.cxx b/chart2/source/view/axes/VPolarGrid.cxx new file mode 100644 index 000000000..e876c83b2 --- /dev/null +++ b/chart2/source/view/axes/VPolarGrid.cxx @@ -0,0 +1,247 @@ +/* -*- 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 "VPolarGrid.hxx" +#include "VCartesianGrid.hxx" +#include "Tickmarks.hxx" +#include <PlottingPositionHelper.hxx> +#include <ShapeFactory.hxx> +#include <ObjectIdentifier.hxx> +#include <CommonConverters.hxx> +#include <VLineProperties.hxx> +#include "Tickmarks_Equidistant.hxx" + +#include <osl/diagnose.h> + +#include <vector> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; + +VPolarGrid::VPolarGrid( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , std::vector< Reference< beans::XPropertySet > > aGridPropertiesList ) + : VAxisOrGridBase( nDimensionIndex, nDimensionCount ) + , m_aGridPropertiesList( std::move(aGridPropertiesList) ) + , m_pPosHelper( new PolarPlottingPositionHelper() ) +{ + PlotterBase::m_pPosHelper = m_pPosHelper.get(); +} + +VPolarGrid::~VPolarGrid() +{ +} + +void VPolarGrid::setIncrements( std::vector< ExplicitIncrementData >&& rIncrements ) +{ + m_aIncrements = std::move(rIncrements); +} + +void VPolarGrid::getAllTickInfos( sal_Int32 nDimensionIndex, TickInfoArraysType& rAllTickInfos ) const +{ + const std::vector<ExplicitScaleData>& rScales = m_pPosHelper->getScales(); + TickFactory aTickFactory(rScales[nDimensionIndex], m_aIncrements[nDimensionIndex]); + aTickFactory.getAllTicks( rAllTickInfos ); +} + +void VPolarGrid::createLinePointSequence_ForAngleAxis( + drawing::PointSequenceSequence& rPoints + , TickInfoArraysType& rAllTickInfos + , const ExplicitIncrementData& rIncrement + , const ExplicitScaleData& rScale + , PolarPlottingPositionHelper const * pPosHelper + , double fLogicRadius, double fLogicZ ) +{ + Reference< XScaling > xInverseScaling; + if( rScale.Scaling.is() ) + xInverseScaling = rScale.Scaling->getInverseScaling(); + + sal_Int32 nTick = 0; + EquidistantTickIter aIter( rAllTickInfos, rIncrement, 0 ); + auto pPoints = rPoints.getArray(); + for( TickInfo* pTickInfo = aIter.firstInfo() + ; pTickInfo + ; pTickInfo = aIter.nextInfo(), nTick++ ) + { + if(nTick>=rPoints[0].getLength()) + pPoints[0].realloc(rPoints[0].getLength()+30); + auto pPoints0 = pPoints[0].getArray(); + + //xxxxx pTickInfo->updateUnscaledValue( xInverseScaling ); + double fLogicAngle = pTickInfo->getUnscaledTickValue(); + + drawing::Position3D aScenePosition3D( pPosHelper->transformAngleRadiusToScene( fLogicAngle, fLogicRadius, fLogicZ ) ); + pPoints0[nTick].X = static_cast<sal_Int32>(aScenePosition3D.PositionX); + pPoints0[nTick].Y = static_cast<sal_Int32>(aScenePosition3D.PositionY); + } + if(rPoints[0].getLength()>1) + { + pPoints[0].realloc(nTick+1); + auto pPoints0 = pPoints[0].getArray(); + pPoints0[nTick].X = rPoints[0][0].X; + pPoints0[nTick].Y = rPoints[0][0].Y; + } + else + pPoints[0].realloc(0); +} +#ifdef NOTYET +void VPolarGrid::create2DAngleGrid( const Reference< drawing::XShapes >& xLogicTarget + , TickInfoArraysType& /* rRadiusTickInfos */ + , TickInfoArraysType& rAngleTickInfos + , const std::vector<VLineProperties>& rLinePropertiesList ) +{ + Reference< drawing::XShapes > xMainTarget( + createGroupShape( xLogicTarget, m_aCID ) ); + + const std::vector<ExplicitScaleData>& rScales = m_pPosHelper->getScales(); + const ExplicitScaleData& rAngleScale = rScales[0]; + Reference< XScaling > xInverseScaling( NULL ); + if( rAngleScale.Scaling.is() ) + xInverseScaling = rAngleScale.Scaling->getInverseScaling(); + + double fLogicInnerRadius = m_pPosHelper->getInnerLogicRadius(); + double fLogicOuterRadius = m_pPosHelper->getOuterLogicRadius(); + + sal_Int32 nLinePropertiesCount = rLinePropertiesList.size(); + if(nLinePropertiesCount) + { + double fLogicZ = 1.0;//as defined + sal_Int32 nDepth=0; + //create axis main lines + drawing::PointSequenceSequence aAllPoints; + for (auto const& tick : rAngleTickInfos[0]) + { + if( !tick.bPaintIt ) + continue; + + //xxxxx rTickInfo.updateUnscaledValue( xInverseScaling ); + double fLogicAngle = tick.getUnscaledTickValue(); + + drawing::PointSequenceSequence aPoints(1); + aPoints[0].realloc(2); + drawing::Position3D aScenePositionStart( m_pPosHelper->transformAngleRadiusToScene( fLogicAngle, fLogicInnerRadius, fLogicZ ) ); + drawing::Position3D aScenePositionEnd( m_pPosHelper->transformAngleRadiusToScene( fLogicAngle, fLogicOuterRadius, fLogicZ ) ); + aPoints[0][0].X = static_cast<sal_Int32>(aScenePositionStart.PositionX); + aPoints[0][0].Y = static_cast<sal_Int32>(aScenePositionStart.PositionY); + aPoints[0][1].X = static_cast<sal_Int32>(aScenePositionEnd.PositionX); + aPoints[0][1].Y = static_cast<sal_Int32>(aScenePositionEnd.PositionY); + appendPointSequence( aAllPoints, aPoints ); + } + + rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D( + xMainTarget, aAllPoints, &rLinePropertiesList[nDepth] ); + //because of this name this line will be used for marking + m_pShapeFactory->setShapeName( xShape, "MarkHandles" ); + } +} +#endif + +void VPolarGrid::create2DRadiusGrid( const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget + , TickInfoArraysType& rRadiusTickInfos + , TickInfoArraysType& rAngleTickInfos + , const std::vector<VLineProperties>& rLinePropertiesList ) +{ + rtl::Reference<SvxShapeGroupAnyD> xMainTarget = + createGroupShape( xLogicTarget, m_aCID ); + + const std::vector<ExplicitScaleData>& rScales = m_pPosHelper->getScales(); + const ExplicitScaleData& rRadiusScale = rScales[1]; + const ExplicitScaleData& rAngleScale = rScales[0]; + const ExplicitIncrementData& rAngleIncrement = m_aIncrements[0]; + Reference< XScaling > xInverseRadiusScaling; + if( rRadiusScale.Scaling.is() ) + xInverseRadiusScaling = rRadiusScale.Scaling->getInverseScaling(); + + sal_Int32 nLinePropertiesCount = rLinePropertiesList.size(); + TickInfoArraysType::iterator aDepthIter = rRadiusTickInfos.begin(); + const TickInfoArraysType::const_iterator aDepthEnd = rRadiusTickInfos.end(); + for( sal_Int32 nDepth=0 + ; aDepthIter != aDepthEnd && nDepth < nLinePropertiesCount + ; ++aDepthIter, nDepth++ ) + { + if( !rLinePropertiesList[nDepth].isLineVisible() ) + continue; + + rtl::Reference<SvxShapeGroupAnyD> xTarget( xMainTarget ); + if( nDepth > 0 ) + { + xTarget = createGroupShape( xLogicTarget + , ObjectIdentifier::addChildParticle( m_aCID, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_SUBGRID, nDepth-1 ) ) + ); + if(!xTarget.is()) + xTarget = xMainTarget; + } + + //create axis main lines + drawing::PointSequenceSequence aAllPoints; + for (auto const& tick : *aDepthIter) + { + if( !tick.bPaintIt ) + continue; + + //xxxxx rTickInfo.updateUnscaledValue( xInverseRadiusScaling ); + double fLogicRadius = tick.getUnscaledTickValue(); + double const fLogicZ = 1.0;//as defined + + drawing::PointSequenceSequence aPoints(1); + VPolarGrid::createLinePointSequence_ForAngleAxis( aPoints, rAngleTickInfos + , rAngleIncrement, rAngleScale, m_pPosHelper.get(), fLogicRadius, fLogicZ ); + if(aPoints[0].getLength()) + appendPointSequence( aAllPoints, aPoints ); + } + + rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D( + xTarget, aAllPoints, &rLinePropertiesList[nDepth] ); + //because of this name this line will be used for marking + ::chart::ShapeFactory::setShapeName( xShape, "MarkHandles" ); + } +} + +void VPolarGrid::createShapes() +{ + OSL_PRECOND(m_xLogicTarget.is()&&m_xFinalTarget.is(),"Axis is not proper initialized"); + if(!(m_xLogicTarget.is()&&m_xFinalTarget.is())) + return; + if(m_aGridPropertiesList.empty()) + return; + + //create all scaled tickmark values + TickInfoArraysType aAngleTickInfos; + TickInfoArraysType aRadiusTickInfos; + getAllTickInfos( 0, aAngleTickInfos ); + getAllTickInfos( 1, aRadiusTickInfos ); + + std::vector<VLineProperties> aLinePropertiesList; + VCartesianGrid::fillLinePropertiesFromGridModel( aLinePropertiesList, m_aGridPropertiesList ); + + //create tick mark line shapes + if(m_nDimension==2) + { + if(m_nDimensionIndex==1) + create2DRadiusGrid( m_xLogicTarget, aRadiusTickInfos, aAngleTickInfos, aLinePropertiesList ); + //else //no Angle Grid so far as this equals exactly the y axis positions + // create2DAngleGrid( m_xLogicTarget, aRadiusTickInfos, aAngleTickInfos, aLinePropertiesList ); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarGrid.hxx b/chart2/source/view/axes/VPolarGrid.hxx new file mode 100644 index 000000000..9b15d0e5b --- /dev/null +++ b/chart2/source/view/axes/VPolarGrid.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ +#pragma once + +#include "VAxisOrGridBase.hxx" +#include "Tickmarks.hxx" +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <memory> + +namespace chart { struct VLineProperties; } + +namespace chart +{ + +class PolarPlottingPositionHelper; + +class VPolarGrid : public VAxisOrGridBase +{ +// public methods +public: + VPolarGrid( sal_Int32 nDimensionIndex, sal_Int32 nDimensionCount + , std::vector< + css::uno::Reference< css::beans::XPropertySet > > aGridPropertiesList //main grid, subgrid, subsubgrid etc + ); + virtual ~VPolarGrid() override; + + virtual void createShapes() override; + + void setIncrements( std::vector< ExplicitIncrementData >&& rIncrements ); + + static void createLinePointSequence_ForAngleAxis( + css::drawing::PointSequenceSequence& rPoints + , TickInfoArraysType& rAllTickInfos + , const ExplicitIncrementData& rIncrement + , const ExplicitScaleData& rScale + , PolarPlottingPositionHelper const * pPosHelper + , double fLogicRadius, double fLogicZ ); + +private: //member + std::vector< + css::uno::Reference< css::beans::XPropertySet > > m_aGridPropertiesList;//main grid, subgrid, subsubgrid etc + std::unique_ptr<PolarPlottingPositionHelper> m_pPosHelper; + std::vector< ExplicitIncrementData > m_aIncrements; + + void getAllTickInfos( sal_Int32 nDimensionIndex, TickInfoArraysType& rAllTickInfos ) const; + + void create2DRadiusGrid( const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget + , TickInfoArraysType& rRadiusTickInfos + , TickInfoArraysType& rAngleTickInfos + , const std::vector<VLineProperties>& rLinePropertiesList ); +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarRadiusAxis.cxx b/chart2/source/view/axes/VPolarRadiusAxis.cxx new file mode 100644 index 000000000..f93315410 --- /dev/null +++ b/chart2/source/view/axes/VPolarRadiusAxis.cxx @@ -0,0 +1,165 @@ +/* -*- 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 "VPolarRadiusAxis.hxx" +#include "VCartesianAxis.hxx" +#include <PlottingPositionHelper.hxx> +#include <Axis.hxx> +#include <CommonConverters.hxx> +#include "Tickmarks_Equidistant.hxx" + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +VPolarRadiusAxis::VPolarRadiusAxis( const AxisProperties& rAxisProperties + , const uno::Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionCount ) + : VPolarAxis( rAxisProperties, xNumberFormatsSupplier, 1/*nDimensionIndex*/, nDimensionCount ) +{ + m_aAxisProperties.maLabelAlignment.mfLabelDirection = 0.0; + m_aAxisProperties.maLabelAlignment.mfInnerTickDirection = 0.0; + m_aAxisProperties.maLabelAlignment.meAlignment = LABEL_ALIGN_RIGHT; + m_aAxisProperties.m_bIsMainAxis=false; + m_aAxisProperties.init(); + + m_apAxisWithLabels.reset( new VCartesianAxis( + m_aAxisProperties,xNumberFormatsSupplier,1/*nDimensionIndex*/,nDimensionCount + ,new PolarPlottingPositionHelper() ) ); +} + +VPolarRadiusAxis::~VPolarRadiusAxis() +{ +} + +void VPolarRadiusAxis::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix) +{ + VPolarAxis::setTransformationSceneToScreen( rMatrix ); + m_apAxisWithLabels->setTransformationSceneToScreen( rMatrix ); +} + +void VPolarRadiusAxis::setExplicitScaleAndIncrement( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ) +{ + VPolarAxis::setExplicitScaleAndIncrement( rScale, rIncrement ); + m_apAxisWithLabels->setExplicitScaleAndIncrement( rScale, rIncrement ); +} + +void VPolarRadiusAxis::initPlotter( const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget + , const rtl::Reference<SvxShapeGroupAnyD>& xFinalTarget + , const OUString& rCID ) +{ + VPolarAxis::initPlotter( xLogicTarget, xFinalTarget, rCID ); + m_apAxisWithLabels->initPlotter( xLogicTarget, xFinalTarget, rCID ); +} + +void VPolarRadiusAxis::setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis ) +{ + VPolarAxis::setScales( std::vector(rScales), bSwapXAndYAxis ); + m_apAxisWithLabels->setScales( std::move(rScales), bSwapXAndYAxis ); +} + +void VPolarRadiusAxis::initAxisLabelProperties( const css::awt::Size& rFontReferenceSize + , const css::awt::Rectangle& rMaximumSpaceForLabels ) +{ + VPolarAxis::initAxisLabelProperties( rFontReferenceSize, rMaximumSpaceForLabels ); + m_apAxisWithLabels->initAxisLabelProperties( rFontReferenceSize, rMaximumSpaceForLabels ); +} + +sal_Int32 VPolarRadiusAxis::estimateMaximumAutoMainIncrementCount() +{ + return 2; +} + +bool VPolarRadiusAxis::prepareShapeCreation() +{ + //returns true if all is ready for further shape creation and any shapes need to be created + if( !isAnythingToDraw() ) + return false; + + if( m_xGroupShape_Shapes.is() ) + return true; + + return true; +} + +void VPolarRadiusAxis::createMaximumLabels() +{ + m_apAxisWithLabels->createMaximumLabels(); +} + +void VPolarRadiusAxis::updatePositions() +{ + m_apAxisWithLabels->updatePositions(); +} + +void VPolarRadiusAxis::createLabels() +{ + m_apAxisWithLabels->createLabels(); +} + +void VPolarRadiusAxis::createShapes() +{ + if( !prepareShapeCreation() ) + return; + + const ExplicitScaleData& rAngleScale = m_pPosHelper->getScales()[0]; + const ExplicitIncrementData& rAngleIncrement = m_aIncrements[0]; + + TickInfoArraysType aAngleTickInfos; + TickFactory aAngleTickFactory( rAngleScale, rAngleIncrement ); + aAngleTickFactory.getAllTicks( aAngleTickInfos ); + + uno::Reference< XScaling > xInverseScaling; + if( rAngleScale.Scaling.is() ) + xInverseScaling = rAngleScale.Scaling->getInverseScaling(); + + AxisProperties aAxisProperties(m_aAxisProperties); + + sal_Int32 nTick = 0; + EquidistantTickIter aIter( aAngleTickInfos, rAngleIncrement, 0 ); + for( TickInfo* pTickInfo = aIter.firstInfo() + ; pTickInfo; pTickInfo = aIter.nextInfo(), nTick++ ) + { + if( nTick == 0 ) + { + m_apAxisWithLabels->createShapes(); + continue; + } + + //xxxxx pTickInfo->updateUnscaledValue( xInverseScaling ); + aAxisProperties.m_pfMainLinePositionAtOtherAxis = pTickInfo->getUnscaledTickValue(); + aAxisProperties.m_bDisplayLabels=false; + + VCartesianAxis aAxis(aAxisProperties,m_xNumberFormatsSupplier + ,1,2,new PolarPlottingPositionHelper()); + aAxis.setExplicitScaleAndIncrement( m_aScale, m_aIncrement ); + aAxis.initPlotter(m_xLogicTarget,m_xFinalTarget, m_aCID ); + aAxis.setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix( m_aMatrixScreenToScene ) ); + aAxis.setScales( std::vector(m_pPosHelper->getScales()), false ); + aAxis.initAxisLabelProperties(m_aAxisLabelProperties.m_aFontReferenceSize,m_aAxisLabelProperties.m_aMaximumSpaceForLabels); + aAxis.createShapes(); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/axes/VPolarRadiusAxis.hxx b/chart2/source/view/axes/VPolarRadiusAxis.hxx new file mode 100644 index 000000000..b2450e236 --- /dev/null +++ b/chart2/source/view/axes/VPolarRadiusAxis.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ +#pragma once + +#include "VPolarAxis.hxx" +#include <memory> + +namespace chart +{ + +class VCartesianAxis; + +class VPolarRadiusAxis : public VPolarAxis +{ +public: + VPolarRadiusAxis( const AxisProperties& rAxisProperties + , const css::uno::Reference< css::util::XNumberFormatsSupplier >& xNumberFormatsSupplier + , sal_Int32 nDimensionCount ); + virtual ~VPolarRadiusAxis() override; + + virtual void initPlotter( + const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget + , const rtl::Reference<SvxShapeGroupAnyD>& xFinalTarget + , const OUString& rCID + ) override; + + virtual void setTransformationSceneToScreen( const css::drawing::HomogenMatrix& rMatrix ) override; + + virtual void setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis ) override; + + virtual void setExplicitScaleAndIncrement( + const ExplicitScaleData& rScale + , const ExplicitIncrementData& rIncrement ) override; + + virtual void initAxisLabelProperties( + const css::awt::Size& rFontReferenceSize + , const css::awt::Rectangle& rMaximumSpaceForLabels ) override; + + virtual sal_Int32 estimateMaximumAutoMainIncrementCount() override; + + virtual void createMaximumLabels() override; + virtual void createLabels() override; + virtual void updatePositions() override; + + virtual void createShapes() override; + +protected: //methods + virtual bool prepareShapeCreation() override; + +private: //member + std::unique_ptr<VCartesianAxis> m_apAxisWithLabels; +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |