summaryrefslogtreecommitdiffstats
path: root/chart2/source/view/axes
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /chart2/source/view/axes
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'chart2/source/view/axes')
-rw-r--r--chart2/source/view/axes/DateHelper.cxx93
-rw-r--r--chart2/source/view/axes/DateScaling.cxx205
-rw-r--r--chart2/source/view/axes/DateScaling.hxx95
-rw-r--r--chart2/source/view/axes/MinimumAndMaximumSupplier.cxx203
-rw-r--r--chart2/source/view/axes/ScaleAutomatism.cxx989
-rw-r--r--chart2/source/view/axes/TickmarkProperties.hxx37
-rw-r--r--chart2/source/view/axes/Tickmarks.cxx317
-rw-r--r--chart2/source/view/axes/Tickmarks.hxx162
-rw-r--r--chart2/source/view/axes/Tickmarks_Dates.cxx150
-rw-r--r--chart2/source/view/axes/Tickmarks_Dates.hxx49
-rw-r--r--chart2/source/view/axes/Tickmarks_Equidistant.cxx625
-rw-r--r--chart2/source/view/axes/Tickmarks_Equidistant.hxx146
-rw-r--r--chart2/source/view/axes/VAxisBase.cxx244
-rw-r--r--chart2/source/view/axes/VAxisBase.hxx102
-rw-r--r--chart2/source/view/axes/VAxisOrGridBase.cxx70
-rw-r--r--chart2/source/view/axes/VAxisOrGridBase.hxx63
-rw-r--r--chart2/source/view/axes/VAxisProperties.cxx392
-rw-r--r--chart2/source/view/axes/VAxisProperties.hxx164
-rw-r--r--chart2/source/view/axes/VCartesianAxis.cxx1971
-rw-r--r--chart2/source/view/axes/VCartesianAxis.hxx160
-rw-r--r--chart2/source/view/axes/VCartesianCoordinateSystem.cxx216
-rw-r--r--chart2/source/view/axes/VCartesianCoordinateSystem.hxx47
-rw-r--r--chart2/source/view/axes/VCartesianGrid.cxx309
-rw-r--r--chart2/source/view/axes/VCartesianGrid.hxx52
-rw-r--r--chart2/source/view/axes/VCoordinateSystem.cxx576
-rw-r--r--chart2/source/view/axes/VPolarAngleAxis.cxx205
-rw-r--r--chart2/source/view/axes/VPolarAngleAxis.hxx51
-rw-r--r--chart2/source/view/axes/VPolarAxis.cxx64
-rw-r--r--chart2/source/view/axes/VPolarAxis.hxx54
-rw-r--r--chart2/source/view/axes/VPolarCoordinateSystem.cxx190
-rw-r--r--chart2/source/view/axes/VPolarCoordinateSystem.hxx51
-rw-r--r--chart2/source/view/axes/VPolarGrid.cxx247
-rw-r--r--chart2/source/view/axes/VPolarGrid.hxx71
-rw-r--r--chart2/source/view/axes/VPolarRadiusAxis.cxx165
-rw-r--r--chart2/source/view/axes/VPolarRadiusAxis.hxx72
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: */